/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.os; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; import com.android.internal.util.Preconditions; import java.io.Closeable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.Reference; import java.util.Objects; /** The PerformanceHintManager allows apps to send performance hint to system. */ @SystemService(Context.PERFORMANCE_HINT_SERVICE) public final class PerformanceHintManager { private final long mNativeManagerPtr; /** @hide */ public static PerformanceHintManager create() throws ServiceManager.ServiceNotFoundException { long nativeManagerPtr = nativeAcquireManager(); if (nativeManagerPtr == 0) { throw new ServiceManager.ServiceNotFoundException(Context.PERFORMANCE_HINT_SERVICE); } return new PerformanceHintManager(nativeManagerPtr); } private PerformanceHintManager(long nativeManagerPtr) { mNativeManagerPtr = nativeManagerPtr; } /** * Get preferred update rate information for this device. * * @return the preferred update rate supported by device software */ public long getPreferredUpdateRateNanos() { return nativeGetPreferredUpdateRateNanos(mNativeManagerPtr); } /** * Creates a {@link Session} for the given set of threads and sets their initial target work * duration. * * @param tids The list of threads to be associated with this session. They must be part of * this process' thread group * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new * session * @return the new session if it is supported on this device, null if hint session is not * supported on this device or the tid doesn't belong to the application * @throws IllegalArgumentException if the thread id list is empty, or * initialTargetWorkDurationNanos is non-positive */ @Nullable public Session createHintSession(@NonNull int[] tids, long initialTargetWorkDurationNanos) { Objects.requireNonNull(tids, "tids cannot be null"); if (tids.length == 0) { throw new IllegalArgumentException("thread id list can't be empty."); } Preconditions.checkArgumentPositive(initialTargetWorkDurationNanos, "the hint target duration should be positive."); long nativeSessionPtr = nativeCreateSession(mNativeManagerPtr, tids, initialTargetWorkDurationNanos); if (nativeSessionPtr == 0) return null; return new Session(nativeSessionPtr); } /** * A Session represents a group of threads with an inter-related workload such that hints for * their performance should be considered as a unit. The threads in a given session should be * long-lived and not created or destroyed dynamically. * * The work duration API can be used with periodic workloads to dynamically adjust thread * performance and keep the work on schedule while optimizing the available power budget. * When using the work duration API, the starting target duration should be specified * while creating the session, but can later be adjusted with * {@link #updateTargetWorkDuration(long)}. While using the work duration API, the client is be * expected to call {@link #reportActualWorkDuration(long)} each cycle to report the actual * time taken to complete to the system. * * Any call in this class will change its internal data, so you must do your own thread * safety to protect from racing. * * All timings should be in {@link SystemClock#uptimeNanos()}. */ public static class Session implements Closeable { private long mNativeSessionPtr; /** @hide */ public Session(long nativeSessionPtr) { mNativeSessionPtr = nativeSessionPtr; } /** * This hint indicates a sudden increase in CPU workload intensity. It means * that this hint session needs extra CPU resources immediately to meet the * target duration for the current work cycle. * * @hide */ @TestApi public static final int CPU_LOAD_UP = 0; /** * This hint indicates a decrease in CPU workload intensity. It means that * this hint session can reduce CPU resources and still meet the target duration. * * @hide */ @TestApi public static final int CPU_LOAD_DOWN = 1; /** * This hint indicates an upcoming CPU workload that is completely changed and * unknown. It means that the hint session should reset CPU resources to a known * baseline to prepare for an arbitrary load, and must wake up if inactive. * * @hide */ @TestApi public static final int CPU_LOAD_RESET = 2; /** * This hint indicates that the most recent CPU workload is resuming after a * period of inactivity. It means that the hint session should allocate similar * CPU resources to what was used previously, and must wake up if inactive. * * @hide */ @TestApi public static final int CPU_LOAD_RESUME = 3; /** * This hint indicates an increase in GPU workload intensity. It means that * this hint session needs extra GPU resources to meet the target duration. * This hint must be sent before reporting the actual duration to the session. * * @hide */ @TestApi @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) public static final int GPU_LOAD_UP = 5; /** * This hint indicates a decrease in GPU workload intensity. It means that * this hint session can reduce GPU resources and still meet the target duration. * * @hide */ @TestApi @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) public static final int GPU_LOAD_DOWN = 6; /** * This hint indicates an upcoming GPU workload that is completely changed and * unknown. It means that the hint session should reset GPU resources to a known * baseline to prepare for an arbitrary load, and must wake up if inactive. * * @hide */ @TestApi @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) public static final int GPU_LOAD_RESET = 7; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"CPU_LOAD_", "GPU_LOAD_"}, value = { CPU_LOAD_UP, CPU_LOAD_DOWN, CPU_LOAD_RESET, CPU_LOAD_RESUME, GPU_LOAD_UP, GPU_LOAD_DOWN, GPU_LOAD_RESET }) public @interface Hint {} /** @hide */ @Override protected void finalize() throws Throwable { try { close(); } finally { super.finalize(); } } /** * Updates this session's target total duration for each cycle of work. * * @param targetDurationNanos the new desired duration in nanoseconds */ public void updateTargetWorkDuration(long targetDurationNanos) { Preconditions.checkArgumentPositive(targetDurationNanos, "the hint target duration" + " should be positive."); nativeUpdateTargetWorkDuration(mNativeSessionPtr, targetDurationNanos); } /** * Reports the actual duration for the last cycle of work. * * The system will attempt to adjust the core placement of the threads within the thread * group and/or the frequency of the core on which they are run to bring the actual duration * close to the target duration. * * @param actualDurationNanos how long the thread group took to complete its last task in * nanoseconds */ public void reportActualWorkDuration(long actualDurationNanos) { Preconditions.checkArgumentPositive(actualDurationNanos, "the actual duration should" + " be positive."); nativeReportActualWorkDuration(mNativeSessionPtr, actualDurationNanos); } /** * Ends the current hint session. * * Once called, you should not call anything else on this object. */ public void close() { if (mNativeSessionPtr != 0) { nativeCloseSession(mNativeSessionPtr); mNativeSessionPtr = 0; } } /** * Sends performance hints to inform the hint session of changes in the workload. * * @param hint The hint to send to the session * * @hide */ @TestApi public void sendHint(@Hint int hint) { Preconditions.checkArgumentNonNegative(hint, "the hint ID should be at least" + " zero."); try { nativeSendHint(mNativeSessionPtr, hint); } finally { Reference.reachabilityFence(this); } } /** * This tells the session that these threads can be * safely scheduled to prefer power efficiency over performance. * * @param enabled The flag that sets whether this session uses power-efficient scheduling. */ @FlaggedApi(Flags.FLAG_ADPF_PREFER_POWER_EFFICIENCY) public void setPreferPowerEfficiency(boolean enabled) { nativeSetPreferPowerEfficiency(mNativeSessionPtr, enabled); } /** * Set a list of threads to the performance hint session. This operation will replace * the current list of threads with the given list of threads. * Note that this is not an oneway method. * * @param tids The list of threads to be associated with this session. They must be * part of this app's thread group * * @throws IllegalStateException if the hint session is not in the foreground * @throws IllegalArgumentException if the thread id list is empty * @throws SecurityException if any thread id doesn't belong to the application */ public void setThreads(@NonNull int[] tids) { if (mNativeSessionPtr == 0) { return; } Objects.requireNonNull(tids, "tids cannot be null"); if (tids.length == 0) { throw new IllegalArgumentException("Thread id list can't be empty."); } nativeSetThreads(mNativeSessionPtr, tids); } /** * Returns the list of thread ids. * * @hide */ @TestApi public @Nullable int[] getThreadIds() { return nativeGetThreadIds(mNativeSessionPtr); } /** * Reports the work duration for the last cycle of work. * * The system will attempt to adjust the core placement of the threads within the thread * group and/or the frequency of the core on which they are run to bring the actual duration * close to the target duration. * * @param workDuration the work duration of each component. * @throws IllegalArgumentException if * the work period start timestamp or the total duration are less than or equal to zero, * if either the actual CPU duration or actual GPU duration is less than zero, * or if both the CPU and GPU durations are zero. */ @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) public void reportActualWorkDuration(@NonNull WorkDuration workDuration) { if (workDuration.mWorkPeriodStartTimestampNanos <= 0) { throw new IllegalArgumentException( "the work period start timestamp should be greater than zero."); } if (workDuration.mActualTotalDurationNanos <= 0) { throw new IllegalArgumentException( "the actual total duration should be greater than zero."); } if (workDuration.mActualCpuDurationNanos < 0) { throw new IllegalArgumentException( "the actual CPU duration should be greater than or equal to zero."); } if (workDuration.mActualGpuDurationNanos < 0) { throw new IllegalArgumentException( "the actual GPU duration should be greater than or equal to zero."); } if (workDuration.mActualCpuDurationNanos + workDuration.mActualGpuDurationNanos <= 0) { throw new IllegalArgumentException( "either the actual CPU duration or the actual GPU duration should be greater" + "than zero."); } nativeReportActualWorkDuration(mNativeSessionPtr, workDuration.mWorkPeriodStartTimestampNanos, workDuration.mActualTotalDurationNanos, workDuration.mActualCpuDurationNanos, workDuration.mActualGpuDurationNanos); } } private static native long nativeAcquireManager(); private static native long nativeGetPreferredUpdateRateNanos(long nativeManagerPtr); private static native long nativeCreateSession(long nativeManagerPtr, int[] tids, long initialTargetWorkDurationNanos); private static native int[] nativeGetThreadIds(long nativeSessionPtr); private static native void nativeUpdateTargetWorkDuration(long nativeSessionPtr, long targetDurationNanos); private static native void nativeReportActualWorkDuration(long nativeSessionPtr, long actualDurationNanos); private static native void nativeCloseSession(long nativeSessionPtr); private static native void nativeSendHint(long nativeSessionPtr, int hint); private static native void nativeSetThreads(long nativeSessionPtr, int[] tids); private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr, boolean enabled); private static native void nativeReportActualWorkDuration(long nativeSessionPtr, long workPeriodStartTimestampNanos, long actualTotalDurationNanos, long actualCpuDurationNanos, long actualGpuDurationNanos); }