Loading core/java/android/os/IHintManager.aidl +12 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.os; import android.os.IHintSession; import android.hardware.power.ChannelConfig; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; Loading @@ -27,6 +28,9 @@ interface IHintManager { * Creates a {@link Session} for the given set of threads and associates to a binder token. * Returns a config if creation is not supported, and HMS had to use the * legacy creation method. * * Throws UnsupportedOperationException if ADPF is not supported, and IllegalStateException * if creation is supported but fails. */ IHintSession createHintSessionWithConfig(in IBinder token, in int[] threadIds, in long durationNanos, in SessionTag tag, out @nullable SessionConfig config); Loading @@ -38,4 +42,12 @@ interface IHintManager { void setHintSessionThreads(in IHintSession hintSession, in int[] tids); int[] getHintSessionThreadIds(in IHintSession hintSession); /** * Returns FMQ channel information for the caller, which it associates to a binder token. * * Throws IllegalStateException if FMQ channel creation fails. */ ChannelConfig getSessionChannel(in IBinder token); oneway void closeSessionChannel(); } native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +4 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ #define LOG_TAG "PerformanceHintNativeTest" #include <aidl/android/hardware/power/ChannelConfig.h> #include <aidl/android/hardware/power/SessionConfig.h> #include <aidl/android/hardware/power/SessionTag.h> #include <aidl/android/hardware/power/WorkDuration.h> Loading Loading @@ -54,6 +55,9 @@ public: MOCK_METHOD(ScopedAStatus, getHintSessionThreadIds, (const std::shared_ptr<IHintSession>& hintSession, ::std::vector<int32_t>* tids), (override)); MOCK_METHOD(ScopedAStatus, getSessionChannel, (const ::ndk::SpAIBinder& in_token, hal::ChannelConfig* _aidl_return), (override)); MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override)); MOCK_METHOD(SpAIBinder, asBinder, (), (override)); MOCK_METHOD(bool, isRemote, (), (override)); }; Loading services/core/java/com/android/server/power/hint/HintManagerService.java +186 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.power.hint; import static android.os.Flags.adpfUseFmqChannel; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.power.hint.Flags.powerhintThreadCleanup; Loading @@ -26,6 +28,8 @@ import android.app.ActivityManagerInternal; import android.app.StatsManager; import android.app.UidObserver; import android.content.Context; import android.hardware.power.ChannelConfig; import android.hardware.power.IPower; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; import android.hardware.power.WorkDuration; Loading @@ -39,6 +43,7 @@ import android.os.Message; import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.text.TextUtils; import android.util.ArrayMap; Loading Loading @@ -69,6 +74,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; Loading @@ -91,9 +97,21 @@ public final class HintManagerService extends SystemService { @GuardedBy("mLock") private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions; // Multi-level map storing all the channel binder token death listeners. // First level is keyed by the UID of the client process owning the channel. // Second level is the tgid of the process, which will often just be size one. // Each channel is unique per (tgid, uid) pair, so this map associates each pair with an // object that listens for the death notification of the binder token that was provided by // that client when it created the channel, so we can detect when the client process dies. @GuardedBy("mChannelMapLock") private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap; /** Lock to protect mActiveSessions and the UidObserver. */ private final Object mLock = new Object(); /** Lock to protect mChannelMap. */ private final Object mChannelMapLock = new Object(); @GuardedBy("mNonIsolatedTidsLock") private final Map<Integer, Set<Long>> mNonIsolatedTids; Loading @@ -110,6 +128,9 @@ public final class HintManagerService extends SystemService { private AtomicBoolean mConfigCreationSupport = new AtomicBoolean(true); private final IPower mPowerHal; private int mPowerHalVersion; private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint"; private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; Loading @@ -131,12 +152,22 @@ public final class HintManagerService extends SystemService { mNonIsolatedTids = null; } mActiveSessions = new ArrayMap<>(); mChannelMap = new ArrayMap<>(); mNativeWrapper = injector.createNativeWrapper(); mNativeWrapper.halInit(); mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); mUidObserver = new MyUidObserver(); mAmInternal = Objects.requireNonNull( LocalServices.getService(ActivityManagerInternal.class)); mPowerHal = injector.createIPower(); mPowerHalVersion = 0; if (mPowerHal != null) { try { mPowerHalVersion = mPowerHal.getInterfaceVersion(); } catch (RemoteException e) { throw new IllegalStateException("Could not contact PowerHAL!", e); } } } private ServiceThread createCleanUpThread() { Loading @@ -151,6 +182,10 @@ public final class HintManagerService extends SystemService { NativeWrapper createNativeWrapper() { return new NativeWrapper(); } IPower createIPower() { return IPower.Stub.asInterface( ServiceManager.waitForDeclaredService(IPower.DESCRIPTOR + "/default")); } } private boolean isHalSupported() { Loading Loading @@ -344,6 +379,16 @@ public final class HintManagerService extends SystemService { } } } synchronized (mChannelMapLock) { // Clean up the uid's session channels final TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid); if (uidMap != null) { for (Map.Entry<Integer, ChannelItem> entry : uidMap.entrySet()) { entry.getValue().closeChannel(); } mChannelMap.remove(uid); } } }); } Loading Loading @@ -383,6 +428,113 @@ public final class HintManagerService extends SystemService { } } /** * Creates a channel item in the channel map if one does not exist, then returns * the entry in the channel map. */ public ChannelItem getOrCreateMappedChannelItem(int tgid, int uid, IBinder token) { synchronized (mChannelMapLock) { if (!mChannelMap.containsKey(uid)) { mChannelMap.put(uid, new TreeMap<Integer, ChannelItem>()); } TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid); if (!map.containsKey(tgid)) { ChannelItem item = new ChannelItem(tgid, uid, token); item.openChannel(); map.put(tgid, item); } return map.get(tgid); } } /** * This removes an entry in the binder token callback map when a channel is closed, * and unregisters its callbacks. */ public void removeChannelItem(Integer tgid, Integer uid) { synchronized (mChannelMapLock) { TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid); if (map != null) { ChannelItem item = map.get(tgid); if (item != null) { item.closeChannel(); map.remove(tgid); } if (map.isEmpty()) { mChannelMap.remove(uid); } } } } /** * Manages the lifecycle of a single channel. This includes caching the channel descriptor, * receiving binder token death notifications, and handling cleanup on uid termination. There * can only be one ChannelItem per (tgid, uid) pair in mChannelMap, and channel creation happens * when a ChannelItem enters the map, while destruction happens when it leaves the map. */ private class ChannelItem implements IBinder.DeathRecipient { @Override public void binderDied() { removeChannelItem(mTgid, mUid); } ChannelItem(int tgid, int uid, IBinder token) { this.mTgid = tgid; this.mUid = uid; this.mToken = token; this.mLinked = false; this.mConfig = null; } public void closeChannel() { if (mLinked) { mToken.unlinkToDeath(this, 0); mLinked = false; } if (mConfig != null) { try { mPowerHal.closeSessionChannel(mTgid, mUid); } catch (RemoteException e) { throw new IllegalStateException("Failed to close session channel!", e); } mConfig = null; } } public void openChannel() { if (!mLinked) { try { mToken.linkToDeath(this, 0); } catch (RemoteException e) { throw new IllegalStateException("Client already dead", e); } mLinked = true; } if (mConfig == null) { try { // This method uses PowerHAL directly through the SDK, // to avoid needing to pass the ChannelConfig through JNI. mConfig = mPowerHal.getSessionChannel(mTgid, mUid); } catch (RemoteException e) { removeChannelItem(mTgid, mUid); throw new IllegalStateException("Failed to create session channel!", e); } } } ChannelConfig getConfig() { return mConfig; } // To avoid accidental double-linking / unlinking boolean mLinked; final int mTgid; final int mUid; final IBinder mToken; ChannelConfig mConfig; } final class CleanUpHandler extends Handler { // status of processed tid used for caching private static final int TID_NOT_CHECKED = 0; Loading Loading @@ -570,6 +722,18 @@ public final class HintManagerService extends SystemService { return mService; } @VisibleForTesting Boolean hasChannel(int tgid, int uid) { synchronized (mChannelMapLock) { TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid); if (uidMap != null) { ChannelItem item = uidMap.get(tgid); return item != null; } return false; } } // returns the first invalid tid or null if not found private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) { // Make sure all tids belongs to the same UID (including isolated UID), Loading Loading @@ -709,6 +873,28 @@ public final class HintManagerService extends SystemService { } } @Override public ChannelConfig getSessionChannel(IBinder token) { if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) { return null; } java.util.Objects.requireNonNull(token); final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final int callingUid = Binder.getCallingUid(); ChannelItem item = getOrCreateMappedChannelItem(callingTgid, callingUid, token); return item.getConfig(); }; @Override public void closeSessionChannel() { if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) { return; } final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final int callingUid = Binder.getCallingUid(); removeChannelItem(callingTgid, callingUid); }; @Override public long getHintSessionPreferredRate() { return mHintSessionPreferredRate; Loading services/core/jni/com_android_server_hint_HintManagerService.cpp +13 −15 Original line number Diff line number Diff line Loading @@ -34,11 +34,8 @@ #include "jni.h" using aidl::android::hardware::power::SessionConfig; using aidl::android::hardware::power::SessionHint; using aidl::android::hardware::power::SessionMode; using aidl::android::hardware::power::SessionTag; using aidl::android::hardware::power::WorkDuration; namespace hal = aidl::android::hardware::power; using android::power::PowerHintSessionWrapper; namespace android { Loading Loading @@ -95,10 +92,11 @@ static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid, static jlong createHintSessionWithConfig(JNIEnv* env, int32_t tgid, int32_t uid, std::vector<int32_t> threadIds, int64_t durationNanos, int32_t sessionTag, SessionConfig& config) { int32_t sessionTag, hal::SessionConfig& config) { auto result = gPowerHalController.createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, static_cast<SessionTag>(sessionTag), static_cast<hal::SessionTag>( sessionTag), &config); if (result.isOk()) { jlong session_ptr = reinterpret_cast<jlong>(result.value().get()); Loading Loading @@ -140,12 +138,12 @@ static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDuration } static void reportActualWorkDuration(int64_t session_ptr, const std::vector<WorkDuration>& actualDurations) { const std::vector<hal::WorkDuration>& actualDurations) { auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->reportActualWorkDuration(actualDurations); } static void sendHint(int64_t session_ptr, SessionHint hint) { static void sendHint(int64_t session_ptr, hal::SessionHint hint) { auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->sendHint(hint); } Loading @@ -155,7 +153,7 @@ static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadId appSession->setThreads(threadIds); } static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) { static void setMode(int64_t session_ptr, hal::SessionMode mode, bool enabled) { auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->setMode(mode, enabled); } Loading Loading @@ -189,7 +187,7 @@ static jlong nativeCreateHintSessionWithConfig(JNIEnv* env, jclass /* clazz */, return 0; } std::vector<int32_t> threadIds(tidArray.get(), tidArray.get() + tidArray.size()); SessionConfig config; hal::SessionConfig config; jlong out = createHintSessionWithConfig(env, tgid, uid, std::move(threadIds), durationNanos, sessionTag, config); if (out <= 0) { Loading Loading @@ -223,7 +221,7 @@ static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlon ScopedLongArrayRO arrayActualDurations(env, actualDurations); ScopedLongArrayRO arrayTimeStamps(env, timeStamps); std::vector<WorkDuration> actualList(arrayActualDurations.size()); std::vector<hal::WorkDuration> actualList(arrayActualDurations.size()); for (size_t i = 0; i < arrayActualDurations.size(); i++) { actualList[i].timeStampNanos = arrayTimeStamps[i]; actualList[i].durationNanos = arrayActualDurations[i]; Loading @@ -232,7 +230,7 @@ static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlon } static void nativeSendHint(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint hint) { sendHint(session_ptr, static_cast<SessionHint>(hint)); sendHint(session_ptr, static_cast<hal::SessionHint>(hint)); } static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jintArray tids) { Loading @@ -244,13 +242,13 @@ static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint mode, jboolean enabled) { setMode(session_ptr, static_cast<SessionMode>(mode), enabled); setMode(session_ptr, static_cast<hal::SessionMode>(mode), enabled); } static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jobjectArray jWorkDurations) { int size = env->GetArrayLength(jWorkDurations); std::vector<WorkDuration> workDurations(size); std::vector<hal::WorkDuration> workDurations(size); for (int i = 0; i < size; i++) { jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i); workDurations[i].workPeriodStartTimestampNanos = Loading services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +153 −0 Original line number Diff line number Diff line Loading @@ -26,12 +26,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; Loading @@ -39,9 +42,12 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.content.Context; import android.hardware.power.ChannelConfig; import android.hardware.power.IPower; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; import android.hardware.power.WorkDuration; Loading @@ -50,6 +56,7 @@ import android.os.IBinder; import android.os.IHintSession; import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; Loading Loading @@ -130,12 +137,15 @@ public class HintManagerServiceTest { @Mock private HintManagerService.NativeWrapper mNativeWrapperMock; @Mock private IPower mIPowerMock; @Mock private ActivityManagerInternal mAmInternalMock; @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private HintManagerService mService; private ChannelConfig mConfig; private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { return new Answer<Long>() { Loading @@ -149,6 +159,9 @@ public class HintManagerServiceTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mConfig = new ChannelConfig(); mConfig.readFlagBitmask = 1; mConfig.writeFlagBitmask = 2; when(mNativeWrapperMock.halGetHintSessionPreferredRate()) .thenReturn(DEFAULT_HINT_PREFERRED_RATE); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), Loading @@ -170,6 +183,8 @@ public class HintManagerServiceTest { any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2], SESSION_IDS[2])); when(mIPowerMock.getInterfaceVersion()).thenReturn(5); when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock); } Loading Loading @@ -252,6 +267,9 @@ public class HintManagerServiceTest { NativeWrapper createNativeWrapper() { return mNativeWrapperMock; } IPower createIPower() { return mIPowerMock; } }); return mService; } Loading @@ -261,6 +279,9 @@ public class HintManagerServiceTest { NativeWrapper createNativeWrapper() { return new NativeWrapperFake(); } IPower createIPower() { return mIPowerMock; } }); return mService; } Loading Loading @@ -728,6 +749,102 @@ public class HintManagerServiceTest { verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean()); } @Test public void testGetChannel() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); // Should only call once, after caching the first call ChannelConfig config = service.getBinderServiceInstance().getSessionChannel(token); ChannelConfig config2 = service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); assertEquals(config.readFlagBitmask, mConfig.readFlagBitmask); assertEquals(config.writeFlagBitmask, mConfig.writeFlagBitmask); assertEquals(config2.readFlagBitmask, mConfig.readFlagBitmask); assertEquals(config2.writeFlagBitmask, mConfig.writeFlagBitmask); } @Test public void testGetChannelTwice() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); service.getBinderServiceInstance().closeSessionChannel(); verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); clearInvocations(mIPowerMock); service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); service.getBinderServiceInstance().closeSessionChannel(); verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); } @Test public void testGetChannelFails() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenThrow(RemoteException.class); assertThrows(IllegalStateException.class, () -> { service.getBinderServiceInstance().getSessionChannel(token); }); } @Test public void testGetChannelBadVersion() throws Exception { when(mIPowerMock.getInterfaceVersion()).thenReturn(3); HintManagerService service = createService(); Binder token = new Binder(); reset(mIPowerMock); when(mIPowerMock.getInterfaceVersion()).thenReturn(3); when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig); ChannelConfig channel = service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(0)).getSessionChannel(eq(TGID), eq(UID)); assertNull(channel); } @Test public void testCloseChannel() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); service.getBinderServiceInstance().getSessionChannel(token); service.getBinderServiceInstance().closeSessionChannel(); verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); } @Test public void testCloseChannelFails() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); service.getBinderServiceInstance().getSessionChannel(token); doThrow(RemoteException.class).when(mIPowerMock).closeSessionChannel(anyInt(), anyInt()); assertThrows(IllegalStateException.class, () -> { service.getBinderServiceInstance().closeSessionChannel(); }); } @Test public void testDoubleClose() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); service.getBinderServiceInstance().getSessionChannel(token); service.getBinderServiceInstance().closeSessionChannel(); service.getBinderServiceInstance().closeSessionChannel(); verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); } // This test checks that concurrent operations from different threads on IHintService, // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also // check the output of threads' reportActualDuration performance to detect lock starvation Loading Loading @@ -935,4 +1052,40 @@ public class HintManagerServiceTest { a.reportActualWorkDuration2(WORK_DURATIONS_FIVE); verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); } @Test public void testChannelDiesWhenTokenDies() throws Exception { HintManagerService service = createService(); class DyingToken extends Binder { DeathRecipient mToNotify; @Override public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { mToNotify = recipient; super.linkToDeath(recipient, flags); } public void fakeDeath() { mToNotify.binderDied(); } } DyingToken token = new DyingToken(); service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); assertTrue(service.hasChannel(TGID, UID)); token.fakeDeath(); assertFalse(service.hasChannel(TGID, UID)); verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); clearInvocations(mIPowerMock); token = new DyingToken(); service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); assertTrue(service.hasChannel(TGID, UID)); } } Loading
core/java/android/os/IHintManager.aidl +12 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.os; import android.os.IHintSession; import android.hardware.power.ChannelConfig; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; Loading @@ -27,6 +28,9 @@ interface IHintManager { * Creates a {@link Session} for the given set of threads and associates to a binder token. * Returns a config if creation is not supported, and HMS had to use the * legacy creation method. * * Throws UnsupportedOperationException if ADPF is not supported, and IllegalStateException * if creation is supported but fails. */ IHintSession createHintSessionWithConfig(in IBinder token, in int[] threadIds, in long durationNanos, in SessionTag tag, out @nullable SessionConfig config); Loading @@ -38,4 +42,12 @@ interface IHintManager { void setHintSessionThreads(in IHintSession hintSession, in int[] tids); int[] getHintSessionThreadIds(in IHintSession hintSession); /** * Returns FMQ channel information for the caller, which it associates to a binder token. * * Throws IllegalStateException if FMQ channel creation fails. */ ChannelConfig getSessionChannel(in IBinder token); oneway void closeSessionChannel(); }
native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +4 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ #define LOG_TAG "PerformanceHintNativeTest" #include <aidl/android/hardware/power/ChannelConfig.h> #include <aidl/android/hardware/power/SessionConfig.h> #include <aidl/android/hardware/power/SessionTag.h> #include <aidl/android/hardware/power/WorkDuration.h> Loading Loading @@ -54,6 +55,9 @@ public: MOCK_METHOD(ScopedAStatus, getHintSessionThreadIds, (const std::shared_ptr<IHintSession>& hintSession, ::std::vector<int32_t>* tids), (override)); MOCK_METHOD(ScopedAStatus, getSessionChannel, (const ::ndk::SpAIBinder& in_token, hal::ChannelConfig* _aidl_return), (override)); MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override)); MOCK_METHOD(SpAIBinder, asBinder, (), (override)); MOCK_METHOD(bool, isRemote, (), (override)); }; Loading
services/core/java/com/android/server/power/hint/HintManagerService.java +186 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.power.hint; import static android.os.Flags.adpfUseFmqChannel; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.power.hint.Flags.powerhintThreadCleanup; Loading @@ -26,6 +28,8 @@ import android.app.ActivityManagerInternal; import android.app.StatsManager; import android.app.UidObserver; import android.content.Context; import android.hardware.power.ChannelConfig; import android.hardware.power.IPower; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; import android.hardware.power.WorkDuration; Loading @@ -39,6 +43,7 @@ import android.os.Message; import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.text.TextUtils; import android.util.ArrayMap; Loading Loading @@ -69,6 +74,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; Loading @@ -91,9 +97,21 @@ public final class HintManagerService extends SystemService { @GuardedBy("mLock") private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions; // Multi-level map storing all the channel binder token death listeners. // First level is keyed by the UID of the client process owning the channel. // Second level is the tgid of the process, which will often just be size one. // Each channel is unique per (tgid, uid) pair, so this map associates each pair with an // object that listens for the death notification of the binder token that was provided by // that client when it created the channel, so we can detect when the client process dies. @GuardedBy("mChannelMapLock") private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap; /** Lock to protect mActiveSessions and the UidObserver. */ private final Object mLock = new Object(); /** Lock to protect mChannelMap. */ private final Object mChannelMapLock = new Object(); @GuardedBy("mNonIsolatedTidsLock") private final Map<Integer, Set<Long>> mNonIsolatedTids; Loading @@ -110,6 +128,9 @@ public final class HintManagerService extends SystemService { private AtomicBoolean mConfigCreationSupport = new AtomicBoolean(true); private final IPower mPowerHal; private int mPowerHalVersion; private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint"; private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; Loading @@ -131,12 +152,22 @@ public final class HintManagerService extends SystemService { mNonIsolatedTids = null; } mActiveSessions = new ArrayMap<>(); mChannelMap = new ArrayMap<>(); mNativeWrapper = injector.createNativeWrapper(); mNativeWrapper.halInit(); mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); mUidObserver = new MyUidObserver(); mAmInternal = Objects.requireNonNull( LocalServices.getService(ActivityManagerInternal.class)); mPowerHal = injector.createIPower(); mPowerHalVersion = 0; if (mPowerHal != null) { try { mPowerHalVersion = mPowerHal.getInterfaceVersion(); } catch (RemoteException e) { throw new IllegalStateException("Could not contact PowerHAL!", e); } } } private ServiceThread createCleanUpThread() { Loading @@ -151,6 +182,10 @@ public final class HintManagerService extends SystemService { NativeWrapper createNativeWrapper() { return new NativeWrapper(); } IPower createIPower() { return IPower.Stub.asInterface( ServiceManager.waitForDeclaredService(IPower.DESCRIPTOR + "/default")); } } private boolean isHalSupported() { Loading Loading @@ -344,6 +379,16 @@ public final class HintManagerService extends SystemService { } } } synchronized (mChannelMapLock) { // Clean up the uid's session channels final TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid); if (uidMap != null) { for (Map.Entry<Integer, ChannelItem> entry : uidMap.entrySet()) { entry.getValue().closeChannel(); } mChannelMap.remove(uid); } } }); } Loading Loading @@ -383,6 +428,113 @@ public final class HintManagerService extends SystemService { } } /** * Creates a channel item in the channel map if one does not exist, then returns * the entry in the channel map. */ public ChannelItem getOrCreateMappedChannelItem(int tgid, int uid, IBinder token) { synchronized (mChannelMapLock) { if (!mChannelMap.containsKey(uid)) { mChannelMap.put(uid, new TreeMap<Integer, ChannelItem>()); } TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid); if (!map.containsKey(tgid)) { ChannelItem item = new ChannelItem(tgid, uid, token); item.openChannel(); map.put(tgid, item); } return map.get(tgid); } } /** * This removes an entry in the binder token callback map when a channel is closed, * and unregisters its callbacks. */ public void removeChannelItem(Integer tgid, Integer uid) { synchronized (mChannelMapLock) { TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid); if (map != null) { ChannelItem item = map.get(tgid); if (item != null) { item.closeChannel(); map.remove(tgid); } if (map.isEmpty()) { mChannelMap.remove(uid); } } } } /** * Manages the lifecycle of a single channel. This includes caching the channel descriptor, * receiving binder token death notifications, and handling cleanup on uid termination. There * can only be one ChannelItem per (tgid, uid) pair in mChannelMap, and channel creation happens * when a ChannelItem enters the map, while destruction happens when it leaves the map. */ private class ChannelItem implements IBinder.DeathRecipient { @Override public void binderDied() { removeChannelItem(mTgid, mUid); } ChannelItem(int tgid, int uid, IBinder token) { this.mTgid = tgid; this.mUid = uid; this.mToken = token; this.mLinked = false; this.mConfig = null; } public void closeChannel() { if (mLinked) { mToken.unlinkToDeath(this, 0); mLinked = false; } if (mConfig != null) { try { mPowerHal.closeSessionChannel(mTgid, mUid); } catch (RemoteException e) { throw new IllegalStateException("Failed to close session channel!", e); } mConfig = null; } } public void openChannel() { if (!mLinked) { try { mToken.linkToDeath(this, 0); } catch (RemoteException e) { throw new IllegalStateException("Client already dead", e); } mLinked = true; } if (mConfig == null) { try { // This method uses PowerHAL directly through the SDK, // to avoid needing to pass the ChannelConfig through JNI. mConfig = mPowerHal.getSessionChannel(mTgid, mUid); } catch (RemoteException e) { removeChannelItem(mTgid, mUid); throw new IllegalStateException("Failed to create session channel!", e); } } } ChannelConfig getConfig() { return mConfig; } // To avoid accidental double-linking / unlinking boolean mLinked; final int mTgid; final int mUid; final IBinder mToken; ChannelConfig mConfig; } final class CleanUpHandler extends Handler { // status of processed tid used for caching private static final int TID_NOT_CHECKED = 0; Loading Loading @@ -570,6 +722,18 @@ public final class HintManagerService extends SystemService { return mService; } @VisibleForTesting Boolean hasChannel(int tgid, int uid) { synchronized (mChannelMapLock) { TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid); if (uidMap != null) { ChannelItem item = uidMap.get(tgid); return item != null; } return false; } } // returns the first invalid tid or null if not found private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) { // Make sure all tids belongs to the same UID (including isolated UID), Loading Loading @@ -709,6 +873,28 @@ public final class HintManagerService extends SystemService { } } @Override public ChannelConfig getSessionChannel(IBinder token) { if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) { return null; } java.util.Objects.requireNonNull(token); final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final int callingUid = Binder.getCallingUid(); ChannelItem item = getOrCreateMappedChannelItem(callingTgid, callingUid, token); return item.getConfig(); }; @Override public void closeSessionChannel() { if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) { return; } final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final int callingUid = Binder.getCallingUid(); removeChannelItem(callingTgid, callingUid); }; @Override public long getHintSessionPreferredRate() { return mHintSessionPreferredRate; Loading
services/core/jni/com_android_server_hint_HintManagerService.cpp +13 −15 Original line number Diff line number Diff line Loading @@ -34,11 +34,8 @@ #include "jni.h" using aidl::android::hardware::power::SessionConfig; using aidl::android::hardware::power::SessionHint; using aidl::android::hardware::power::SessionMode; using aidl::android::hardware::power::SessionTag; using aidl::android::hardware::power::WorkDuration; namespace hal = aidl::android::hardware::power; using android::power::PowerHintSessionWrapper; namespace android { Loading Loading @@ -95,10 +92,11 @@ static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid, static jlong createHintSessionWithConfig(JNIEnv* env, int32_t tgid, int32_t uid, std::vector<int32_t> threadIds, int64_t durationNanos, int32_t sessionTag, SessionConfig& config) { int32_t sessionTag, hal::SessionConfig& config) { auto result = gPowerHalController.createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, static_cast<SessionTag>(sessionTag), static_cast<hal::SessionTag>( sessionTag), &config); if (result.isOk()) { jlong session_ptr = reinterpret_cast<jlong>(result.value().get()); Loading Loading @@ -140,12 +138,12 @@ static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDuration } static void reportActualWorkDuration(int64_t session_ptr, const std::vector<WorkDuration>& actualDurations) { const std::vector<hal::WorkDuration>& actualDurations) { auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->reportActualWorkDuration(actualDurations); } static void sendHint(int64_t session_ptr, SessionHint hint) { static void sendHint(int64_t session_ptr, hal::SessionHint hint) { auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->sendHint(hint); } Loading @@ -155,7 +153,7 @@ static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadId appSession->setThreads(threadIds); } static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) { static void setMode(int64_t session_ptr, hal::SessionMode mode, bool enabled) { auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->setMode(mode, enabled); } Loading Loading @@ -189,7 +187,7 @@ static jlong nativeCreateHintSessionWithConfig(JNIEnv* env, jclass /* clazz */, return 0; } std::vector<int32_t> threadIds(tidArray.get(), tidArray.get() + tidArray.size()); SessionConfig config; hal::SessionConfig config; jlong out = createHintSessionWithConfig(env, tgid, uid, std::move(threadIds), durationNanos, sessionTag, config); if (out <= 0) { Loading Loading @@ -223,7 +221,7 @@ static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlon ScopedLongArrayRO arrayActualDurations(env, actualDurations); ScopedLongArrayRO arrayTimeStamps(env, timeStamps); std::vector<WorkDuration> actualList(arrayActualDurations.size()); std::vector<hal::WorkDuration> actualList(arrayActualDurations.size()); for (size_t i = 0; i < arrayActualDurations.size(); i++) { actualList[i].timeStampNanos = arrayTimeStamps[i]; actualList[i].durationNanos = arrayActualDurations[i]; Loading @@ -232,7 +230,7 @@ static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlon } static void nativeSendHint(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint hint) { sendHint(session_ptr, static_cast<SessionHint>(hint)); sendHint(session_ptr, static_cast<hal::SessionHint>(hint)); } static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jintArray tids) { Loading @@ -244,13 +242,13 @@ static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint mode, jboolean enabled) { setMode(session_ptr, static_cast<SessionMode>(mode), enabled); setMode(session_ptr, static_cast<hal::SessionMode>(mode), enabled); } static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jobjectArray jWorkDurations) { int size = env->GetArrayLength(jWorkDurations); std::vector<WorkDuration> workDurations(size); std::vector<hal::WorkDuration> workDurations(size); for (int i = 0; i < size; i++) { jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i); workDurations[i].workPeriodStartTimestampNanos = Loading
services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +153 −0 Original line number Diff line number Diff line Loading @@ -26,12 +26,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; Loading @@ -39,9 +42,12 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.content.Context; import android.hardware.power.ChannelConfig; import android.hardware.power.IPower; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; import android.hardware.power.WorkDuration; Loading @@ -50,6 +56,7 @@ import android.os.IBinder; import android.os.IHintSession; import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; Loading Loading @@ -130,12 +137,15 @@ public class HintManagerServiceTest { @Mock private HintManagerService.NativeWrapper mNativeWrapperMock; @Mock private IPower mIPowerMock; @Mock private ActivityManagerInternal mAmInternalMock; @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private HintManagerService mService; private ChannelConfig mConfig; private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { return new Answer<Long>() { Loading @@ -149,6 +159,9 @@ public class HintManagerServiceTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mConfig = new ChannelConfig(); mConfig.readFlagBitmask = 1; mConfig.writeFlagBitmask = 2; when(mNativeWrapperMock.halGetHintSessionPreferredRate()) .thenReturn(DEFAULT_HINT_PREFERRED_RATE); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), Loading @@ -170,6 +183,8 @@ public class HintManagerServiceTest { any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2], SESSION_IDS[2])); when(mIPowerMock.getInterfaceVersion()).thenReturn(5); when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock); } Loading Loading @@ -252,6 +267,9 @@ public class HintManagerServiceTest { NativeWrapper createNativeWrapper() { return mNativeWrapperMock; } IPower createIPower() { return mIPowerMock; } }); return mService; } Loading @@ -261,6 +279,9 @@ public class HintManagerServiceTest { NativeWrapper createNativeWrapper() { return new NativeWrapperFake(); } IPower createIPower() { return mIPowerMock; } }); return mService; } Loading Loading @@ -728,6 +749,102 @@ public class HintManagerServiceTest { verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean()); } @Test public void testGetChannel() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); // Should only call once, after caching the first call ChannelConfig config = service.getBinderServiceInstance().getSessionChannel(token); ChannelConfig config2 = service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); assertEquals(config.readFlagBitmask, mConfig.readFlagBitmask); assertEquals(config.writeFlagBitmask, mConfig.writeFlagBitmask); assertEquals(config2.readFlagBitmask, mConfig.readFlagBitmask); assertEquals(config2.writeFlagBitmask, mConfig.writeFlagBitmask); } @Test public void testGetChannelTwice() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); service.getBinderServiceInstance().closeSessionChannel(); verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); clearInvocations(mIPowerMock); service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); service.getBinderServiceInstance().closeSessionChannel(); verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); } @Test public void testGetChannelFails() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenThrow(RemoteException.class); assertThrows(IllegalStateException.class, () -> { service.getBinderServiceInstance().getSessionChannel(token); }); } @Test public void testGetChannelBadVersion() throws Exception { when(mIPowerMock.getInterfaceVersion()).thenReturn(3); HintManagerService service = createService(); Binder token = new Binder(); reset(mIPowerMock); when(mIPowerMock.getInterfaceVersion()).thenReturn(3); when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig); ChannelConfig channel = service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(0)).getSessionChannel(eq(TGID), eq(UID)); assertNull(channel); } @Test public void testCloseChannel() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); service.getBinderServiceInstance().getSessionChannel(token); service.getBinderServiceInstance().closeSessionChannel(); verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); } @Test public void testCloseChannelFails() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); service.getBinderServiceInstance().getSessionChannel(token); doThrow(RemoteException.class).when(mIPowerMock).closeSessionChannel(anyInt(), anyInt()); assertThrows(IllegalStateException.class, () -> { service.getBinderServiceInstance().closeSessionChannel(); }); } @Test public void testDoubleClose() throws Exception { HintManagerService service = createService(); Binder token = new Binder(); service.getBinderServiceInstance().getSessionChannel(token); service.getBinderServiceInstance().closeSessionChannel(); service.getBinderServiceInstance().closeSessionChannel(); verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); } // This test checks that concurrent operations from different threads on IHintService, // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also // check the output of threads' reportActualDuration performance to detect lock starvation Loading Loading @@ -935,4 +1052,40 @@ public class HintManagerServiceTest { a.reportActualWorkDuration2(WORK_DURATIONS_FIVE); verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); } @Test public void testChannelDiesWhenTokenDies() throws Exception { HintManagerService service = createService(); class DyingToken extends Binder { DeathRecipient mToNotify; @Override public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { mToNotify = recipient; super.linkToDeath(recipient, flags); } public void fakeDeath() { mToNotify.binderDied(); } } DyingToken token = new DyingToken(); service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); assertTrue(service.hasChannel(TGID, UID)); token.fakeDeath(); assertFalse(service.hasChannel(TGID, UID)); verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); clearInvocations(mIPowerMock); token = new DyingToken(); service.getBinderServiceInstance().getSessionChannel(token); verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); assertTrue(service.hasChannel(TGID, UID)); } }