1 /*
2  * Copyright (C) 2021 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 android.os;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SystemService;
24 import android.annotation.TestApi;
25 import android.content.Context;
26 
27 import com.android.internal.util.Preconditions;
28 
29 import java.io.Closeable;
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.lang.ref.Reference;
33 import java.util.Objects;
34 
35 
36 /** The PerformanceHintManager allows apps to send performance hint to system. */
37 @SystemService(Context.PERFORMANCE_HINT_SERVICE)
38 public final class PerformanceHintManager {
39     private final long mNativeManagerPtr;
40 
41     /** @hide */
create()42     public static PerformanceHintManager create() throws ServiceManager.ServiceNotFoundException {
43         long nativeManagerPtr = nativeAcquireManager();
44         if (nativeManagerPtr == 0) {
45             throw new ServiceManager.ServiceNotFoundException(Context.PERFORMANCE_HINT_SERVICE);
46         }
47         return new PerformanceHintManager(nativeManagerPtr);
48     }
49 
PerformanceHintManager(long nativeManagerPtr)50     private PerformanceHintManager(long nativeManagerPtr) {
51         mNativeManagerPtr = nativeManagerPtr;
52     }
53 
54     /**
55      * Get preferred update rate information for this device.
56      *
57      * @return the preferred update rate supported by device software
58      */
getPreferredUpdateRateNanos()59     public long getPreferredUpdateRateNanos() {
60         return nativeGetPreferredUpdateRateNanos(mNativeManagerPtr);
61     }
62 
63     /**
64      * Creates a {@link Session} for the given set of threads and sets their initial target work
65      * duration.
66      *
67      * @param tids The list of threads to be associated with this session. They must be part of
68      *     this process' thread group
69      * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new
70      *     session
71      * @return the new session if it is supported on this device, null if hint session is not
72      *     supported on this device or the tid doesn't belong to the application
73      * @throws IllegalArgumentException if the thread id list is empty, or
74      *                                  initialTargetWorkDurationNanos is non-positive
75      */
76     @Nullable
createHintSession(@onNull int[] tids, long initialTargetWorkDurationNanos)77     public Session createHintSession(@NonNull int[] tids, long initialTargetWorkDurationNanos) {
78         Objects.requireNonNull(tids, "tids cannot be null");
79         if (tids.length == 0) {
80             throw new IllegalArgumentException("thread id list can't be empty.");
81         }
82         Preconditions.checkArgumentPositive(initialTargetWorkDurationNanos,
83                 "the hint target duration should be positive.");
84         long nativeSessionPtr = nativeCreateSession(mNativeManagerPtr, tids,
85                 initialTargetWorkDurationNanos);
86         if (nativeSessionPtr == 0) return null;
87         return new Session(nativeSessionPtr);
88     }
89 
90     /**
91      * A Session represents a group of threads with an inter-related workload such that hints for
92      * their performance should be considered as a unit. The threads in a given session should be
93      * long-lived and not created or destroyed dynamically.
94      *
95      * The work duration API can be used with periodic workloads to dynamically adjust thread
96      * performance and keep the work on schedule while optimizing the available power budget.
97      * When using the work duration API, the starting target duration should be specified
98      * while creating the session, but can later be adjusted with
99      * {@link #updateTargetWorkDuration(long)}. While using the work duration API, the client is be
100      * expected to call {@link #reportActualWorkDuration(long)} each cycle to report the actual
101      * time taken to complete to the system.
102      *
103      * Any call in this class will change its internal data, so you must do your own thread
104      * safety to protect from racing.
105      *
106      * All timings should be in {@link SystemClock#uptimeNanos()}.
107      */
108     public static class Session implements Closeable {
109         private long mNativeSessionPtr;
110 
111         /** @hide */
Session(long nativeSessionPtr)112         public Session(long nativeSessionPtr) {
113             mNativeSessionPtr = nativeSessionPtr;
114         }
115 
116         /**
117         * This hint indicates a sudden increase in CPU workload intensity. It means
118         * that this hint session needs extra CPU resources immediately to meet the
119         * target duration for the current work cycle.
120         *
121         * @hide
122         */
123         @TestApi
124         public static final int CPU_LOAD_UP = 0;
125         /**
126         * This hint indicates a decrease in CPU workload intensity. It means that
127         * this hint session can reduce CPU resources and still meet the target duration.
128         *
129         * @hide
130         */
131         @TestApi
132         public static final int CPU_LOAD_DOWN = 1;
133         /**
134         * This hint indicates an upcoming CPU workload that is completely changed and
135         * unknown. It means that the hint session should reset CPU resources to a known
136         * baseline to prepare for an arbitrary load, and must wake up if inactive.
137         *
138         * @hide
139         */
140         @TestApi
141         public static final int CPU_LOAD_RESET = 2;
142         /**
143         * This hint indicates that the most recent CPU workload is resuming after a
144         * period of inactivity. It means that the hint session should allocate similar
145         * CPU resources to what was used previously, and must wake up if inactive.
146         *
147         * @hide
148         */
149         @TestApi
150         public static final int CPU_LOAD_RESUME = 3;
151 
152         /**
153          * This hint indicates an increase in GPU workload intensity. It means that
154          * this hint session needs extra GPU resources to meet the target duration.
155          * This hint must be sent before reporting the actual duration to the session.
156          *
157          * @hide
158          */
159         @TestApi
160         @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
161         public static final int GPU_LOAD_UP = 5;
162 
163         /**
164          * This hint indicates a decrease in GPU workload intensity. It means that
165          * this hint session can reduce GPU resources and still meet the target duration.
166          *
167          * @hide
168          */
169         @TestApi
170         @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
171         public static final int GPU_LOAD_DOWN = 6;
172 
173         /**
174         * This hint indicates an upcoming GPU workload that is completely changed and
175         * unknown. It means that the hint session should reset GPU resources to a known
176         * baseline to prepare for an arbitrary load, and must wake up if inactive.
177          *
178          * @hide
179          */
180         @TestApi
181         @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
182         public static final int GPU_LOAD_RESET = 7;
183 
184         /** @hide */
185         @Retention(RetentionPolicy.SOURCE)
186         @IntDef(prefix = {"CPU_LOAD_", "GPU_LOAD_"}, value = {
187             CPU_LOAD_UP,
188             CPU_LOAD_DOWN,
189             CPU_LOAD_RESET,
190             CPU_LOAD_RESUME,
191             GPU_LOAD_UP,
192             GPU_LOAD_DOWN,
193             GPU_LOAD_RESET
194         })
195         public @interface Hint {}
196 
197         /** @hide */
198         @Override
finalize()199         protected void finalize() throws Throwable {
200             try {
201                 close();
202             } finally {
203                 super.finalize();
204             }
205         }
206 
207         /**
208          * Updates this session's target total duration for each cycle of work.
209          *
210          * @param targetDurationNanos the new desired duration in nanoseconds
211          */
updateTargetWorkDuration(long targetDurationNanos)212         public void updateTargetWorkDuration(long targetDurationNanos) {
213             Preconditions.checkArgumentPositive(targetDurationNanos, "the hint target duration"
214                     + " should be positive.");
215             nativeUpdateTargetWorkDuration(mNativeSessionPtr, targetDurationNanos);
216         }
217 
218         /**
219          * Reports the actual duration for the last cycle of work.
220          *
221          * The system will attempt to adjust the core placement of the threads within the thread
222          * group and/or the frequency of the core on which they are run to bring the actual duration
223          * close to the target duration.
224          *
225          * @param actualDurationNanos how long the thread group took to complete its last task in
226          *     nanoseconds
227          */
reportActualWorkDuration(long actualDurationNanos)228         public void reportActualWorkDuration(long actualDurationNanos) {
229             Preconditions.checkArgumentPositive(actualDurationNanos, "the actual duration should"
230                     + " be positive.");
231             nativeReportActualWorkDuration(mNativeSessionPtr, actualDurationNanos);
232         }
233 
234         /**
235          * Ends the current hint session.
236          *
237          * Once called, you should not call anything else on this object.
238          */
close()239         public void close() {
240             if (mNativeSessionPtr != 0) {
241                 nativeCloseSession(mNativeSessionPtr);
242                 mNativeSessionPtr = 0;
243             }
244         }
245 
246         /**
247          * Sends performance hints to inform the hint session of changes in the workload.
248          *
249          * @param hint The hint to send to the session
250          *
251          * @hide
252          */
253         @TestApi
sendHint(@int int hint)254         public void sendHint(@Hint int hint) {
255             Preconditions.checkArgumentNonNegative(hint, "the hint ID should be at least"
256                     + " zero.");
257             try {
258                 nativeSendHint(mNativeSessionPtr, hint);
259             } finally {
260                 Reference.reachabilityFence(this);
261             }
262         }
263 
264         /**
265          * This tells the session that these threads can be
266          * safely scheduled to prefer power efficiency over performance.
267          *
268          * @param enabled The flag that sets whether this session uses power-efficient scheduling.
269          */
270         @FlaggedApi(Flags.FLAG_ADPF_PREFER_POWER_EFFICIENCY)
setPreferPowerEfficiency(boolean enabled)271         public void setPreferPowerEfficiency(boolean enabled) {
272             nativeSetPreferPowerEfficiency(mNativeSessionPtr, enabled);
273         }
274 
275         /**
276          * Set a list of threads to the performance hint session. This operation will replace
277          * the current list of threads with the given list of threads.
278          * Note that this is not an oneway method.
279          *
280          * @param tids The list of threads to be associated with this session. They must be
281          *     part of this app's thread group
282          *
283          * @throws IllegalStateException if the hint session is not in the foreground
284          * @throws IllegalArgumentException if the thread id list is empty
285          * @throws SecurityException if any thread id doesn't belong to the application
286          */
setThreads(@onNull int[] tids)287         public void setThreads(@NonNull int[] tids) {
288             if (mNativeSessionPtr == 0) {
289                 return;
290             }
291             Objects.requireNonNull(tids, "tids cannot be null");
292             if (tids.length == 0) {
293                 throw new IllegalArgumentException("Thread id list can't be empty.");
294             }
295             nativeSetThreads(mNativeSessionPtr, tids);
296         }
297 
298         /**
299          * Returns the list of thread ids.
300          *
301          * @hide
302          */
303         @TestApi
getThreadIds()304         public @Nullable int[] getThreadIds() {
305             return nativeGetThreadIds(mNativeSessionPtr);
306         }
307 
308         /**
309          * Reports the work duration for the last cycle of work.
310          *
311          * The system will attempt to adjust the core placement of the threads within the thread
312          * group and/or the frequency of the core on which they are run to bring the actual duration
313          * close to the target duration.
314          *
315          * @param workDuration the work duration of each component.
316          * @throws IllegalArgumentException if
317          * the work period start timestamp or the total duration are less than or equal to zero,
318          * if either the actual CPU duration or actual GPU duration is less than zero,
319          * or if both the CPU and GPU durations are zero.
320          */
321         @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
reportActualWorkDuration(@onNull WorkDuration workDuration)322         public void reportActualWorkDuration(@NonNull WorkDuration workDuration) {
323             if (workDuration.mWorkPeriodStartTimestampNanos <= 0) {
324                 throw new IllegalArgumentException(
325                     "the work period start timestamp should be greater than zero.");
326             }
327             if (workDuration.mActualTotalDurationNanos <= 0) {
328                 throw new IllegalArgumentException(
329                     "the actual total duration should be greater than zero.");
330             }
331             if (workDuration.mActualCpuDurationNanos < 0) {
332                 throw new IllegalArgumentException(
333                     "the actual CPU duration should be greater than or equal to zero.");
334             }
335             if (workDuration.mActualGpuDurationNanos < 0) {
336                 throw new IllegalArgumentException(
337                     "the actual GPU duration should be greater than or equal to zero.");
338             }
339             if (workDuration.mActualCpuDurationNanos + workDuration.mActualGpuDurationNanos <= 0) {
340                 throw new IllegalArgumentException(
341                     "either the actual CPU duration or the actual GPU duration should be greater"
342                     + "than zero.");
343             }
344             nativeReportActualWorkDuration(mNativeSessionPtr,
345                     workDuration.mWorkPeriodStartTimestampNanos,
346                     workDuration.mActualTotalDurationNanos,
347                     workDuration.mActualCpuDurationNanos, workDuration.mActualGpuDurationNanos);
348         }
349     }
350 
nativeAcquireManager()351     private static native long nativeAcquireManager();
nativeGetPreferredUpdateRateNanos(long nativeManagerPtr)352     private static native long nativeGetPreferredUpdateRateNanos(long nativeManagerPtr);
nativeCreateSession(long nativeManagerPtr, int[] tids, long initialTargetWorkDurationNanos)353     private static native long nativeCreateSession(long nativeManagerPtr,
354             int[] tids, long initialTargetWorkDurationNanos);
nativeGetThreadIds(long nativeSessionPtr)355     private static native int[] nativeGetThreadIds(long nativeSessionPtr);
nativeUpdateTargetWorkDuration(long nativeSessionPtr, long targetDurationNanos)356     private static native void nativeUpdateTargetWorkDuration(long nativeSessionPtr,
357             long targetDurationNanos);
nativeReportActualWorkDuration(long nativeSessionPtr, long actualDurationNanos)358     private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
359             long actualDurationNanos);
nativeCloseSession(long nativeSessionPtr)360     private static native void nativeCloseSession(long nativeSessionPtr);
nativeSendHint(long nativeSessionPtr, int hint)361     private static native void nativeSendHint(long nativeSessionPtr, int hint);
nativeSetThreads(long nativeSessionPtr, int[] tids)362     private static native void nativeSetThreads(long nativeSessionPtr, int[] tids);
nativeSetPreferPowerEfficiency(long nativeSessionPtr, boolean enabled)363     private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr,
364             boolean enabled);
nativeReportActualWorkDuration(long nativeSessionPtr, long workPeriodStartTimestampNanos, long actualTotalDurationNanos, long actualCpuDurationNanos, long actualGpuDurationNanos)365     private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
366             long workPeriodStartTimestampNanos, long actualTotalDurationNanos,
367             long actualCpuDurationNanos, long actualGpuDurationNanos);
368 }
369