Loading services/core/java/com/android/server/power/hint/HintManagerService.java +324 −11 Original line number Original line Diff line number Diff line Loading @@ -76,6 +76,7 @@ import java.util.List; import java.util.Map; import java.util.Map; import java.util.NoSuchElementException; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; import java.util.Set; import java.util.TreeMap; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit; Loading Loading @@ -109,12 +110,31 @@ public final class HintManagerService extends SystemService { @GuardedBy("mChannelMapLock") @GuardedBy("mChannelMapLock") private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap; private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap; /* * Multi-level map storing the session statistics since last pull from StatsD. * The first level is keyed by the UID of the process owning the session. * The second level is keyed by the tag of the session. The point of separating different * tags is that since different categories (e.g. HWUI vs APP) of the sessions may have different * behaviors. */ @GuardedBy("mSessionSnapshotMapLock") private ArrayMap<Integer, ArrayMap<Integer, AppHintSessionSnapshot>> mSessionSnapshotMap; /** Lock to protect mActiveSessions and the UidObserver. */ /** Lock to protect mActiveSessions and the UidObserver. */ private final Object mLock = new Object(); private final Object mLock = new Object(); /** Lock to protect mChannelMap. */ /** Lock to protect mChannelMap. */ private final Object mChannelMapLock = new Object(); private final Object mChannelMapLock = new Object(); /* * Lock to protect mSessionSnapshotMap. * Nested acquisition of mSessionSnapshotMapLock and mLock should be avoided. * We should grab these separately. * When we need to have nested acquisitions, we should always follow the order of acquiring * mSessionSnapshotMapLock first then mLock. */ private final Object mSessionSnapshotMapLock = new Object(); @GuardedBy("mNonIsolatedTidsLock") @GuardedBy("mNonIsolatedTidsLock") private final Map<Integer, Set<Long>> mNonIsolatedTids; private final Map<Integer, Set<Long>> mNonIsolatedTids; Loading @@ -135,6 +155,8 @@ public final class HintManagerService extends SystemService { private int mPowerHalVersion; private int mPowerHalVersion; private final PackageManager mPackageManager; private final PackageManager mPackageManager; private boolean mUsesFmq; private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint"; 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"; private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; Loading Loading @@ -162,6 +184,7 @@ public final class HintManagerService extends SystemService { } } mActiveSessions = new ArrayMap<>(); mActiveSessions = new ArrayMap<>(); mChannelMap = new ArrayMap<>(); mChannelMap = new ArrayMap<>(); mSessionSnapshotMap = new ArrayMap<>(); mNativeWrapper = injector.createNativeWrapper(); mNativeWrapper = injector.createNativeWrapper(); mNativeWrapper.halInit(); mNativeWrapper.halInit(); mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); Loading @@ -170,6 +193,7 @@ public final class HintManagerService extends SystemService { LocalServices.getService(ActivityManagerInternal.class)); LocalServices.getService(ActivityManagerInternal.class)); mPowerHal = injector.createIPower(); mPowerHal = injector.createIPower(); mPowerHalVersion = 0; mPowerHalVersion = 0; mUsesFmq = false; if (mPowerHal != null) { if (mPowerHal != null) { try { try { mPowerHalVersion = mPowerHal.getInterfaceVersion(); mPowerHalVersion = mPowerHal.getInterfaceVersion(); Loading Loading @@ -197,6 +221,134 @@ public final class HintManagerService extends SystemService { } } } } private class AppHintSessionSnapshot { /* * Per-Uid and Per-SessionTag snapshot that tracks metrics including * number of created sessions, number of power efficienct sessions, and * maximum number of threads in a session. * Given that it's Per-SessionTag, each uid can have multiple snapshots. */ int mCurrentSessionCount; int mMaxConcurrentSession; int mMaxThreadCount; int mPowerEfficientSessionCount; final int mTargetDurationNsCountPQSize = 100; PriorityQueue<TargetDurationRecord> mTargetDurationNsCountPQ; class TargetDurationRecord implements Comparable<TargetDurationRecord> { long mTargetDurationNs; long mTimestamp; int mCount; TargetDurationRecord(long targetDurationNs) { mTargetDurationNs = targetDurationNs; mTimestamp = System.currentTimeMillis(); mCount = 1; } @Override public int compareTo(TargetDurationRecord t) { int tCount = t.getCount(); int thisCount = this.getCount(); // Here we sort in the order of number of count in ascending order. // i.e. the lowest count of target duration is at the head of the queue. // Upon same count, the tiebreaker is the timestamp, the older item will be at the // front of the queue. if (tCount == thisCount) { return (t.getTimestamp() < this.getTimestamp()) ? 1 : -1; } return (tCount < thisCount) ? 1 : -1; } long getTargetDurationNs() { return mTargetDurationNs; } int getCount() { return mCount; } long getTimestamp() { return mTimestamp; } void setCount(int count) { mCount = count; } void setTimestamp() { mTimestamp = System.currentTimeMillis(); } void setTargetDurationNs(long targetDurationNs) { mTargetDurationNs = targetDurationNs; } } AppHintSessionSnapshot() { mCurrentSessionCount = 0; mMaxConcurrentSession = 0; mMaxThreadCount = 0; mPowerEfficientSessionCount = 0; mTargetDurationNsCountPQ = new PriorityQueue<>(1); } void updateUponSessionCreation(int threadCount, long targetDuration) { mCurrentSessionCount += 1; mMaxConcurrentSession = Math.max(mMaxConcurrentSession, mCurrentSessionCount); mMaxThreadCount = Math.max(mMaxThreadCount, threadCount); updateTargetDurationNs(targetDuration); } void updateUponSessionClose() { mCurrentSessionCount -= 1; } void logPowerEfficientSession() { mPowerEfficientSessionCount += 1; } void updateThreadCount(int threadCount) { mMaxThreadCount = Math.max(mMaxThreadCount, threadCount); } void updateTargetDurationNs(long targetDurationNs) { for (TargetDurationRecord t : mTargetDurationNsCountPQ) { if (t.getTargetDurationNs() == targetDurationNs) { t.setCount(t.getCount() + 1); t.setTimestamp(); return; } } if (mTargetDurationNsCountPQ.size() == mTargetDurationNsCountPQSize) { mTargetDurationNsCountPQ.poll(); } mTargetDurationNsCountPQ.add(new TargetDurationRecord(targetDurationNs)); } int getMaxConcurrentSession() { return mMaxConcurrentSession; } int getMaxThreadCount() { return mMaxThreadCount; } int getPowerEfficientSessionCount() { return mPowerEfficientSessionCount; } long[] targetDurationNsList() { final int listSize = 5; long[] targetDurations = new long[listSize]; while (mTargetDurationNsCountPQ.size() > listSize) { mTargetDurationNsCountPQ.poll(); } for (int i = 0; i < listSize && !mTargetDurationNsCountPQ.isEmpty(); ++i) { targetDurations[i] = mTargetDurationNsCountPQ.poll().getTargetDurationNs(); } return targetDurations; } } private boolean isHalSupported() { private boolean isHalSupported() { return mHintSessionPreferredRate != -1; return mHintSessionPreferredRate != -1; } } Loading Loading @@ -235,6 +387,11 @@ public final class HintManagerService extends SystemService { null, // use default PullAtomMetadata values null, // use default PullAtomMetadata values DIRECT_EXECUTOR, DIRECT_EXECUTOR, this::onPullAtom); this::onPullAtom); statsManager.setPullAtomCallback( FrameworkStatsLog.ADPF_SESSION_SNAPSHOT, null, // use default PullAtomMetadata values DIRECT_EXECUTOR, this::onPullAtom); } } private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) { private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) { Loading @@ -247,11 +404,82 @@ public final class HintManagerService extends SystemService { data.add(FrameworkStatsLog.buildStatsEvent( data.add(FrameworkStatsLog.buildStatsEvent( FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO, FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO, isSurfaceFlingerUsingCpuHint, isSurfaceFlingerUsingCpuHint, isHwuiHintManagerEnabled)); isHwuiHintManagerEnabled, getFmqUsage())); } if (atomTag == FrameworkStatsLog.ADPF_SESSION_SNAPSHOT) { synchronized (mSessionSnapshotMapLock) { for (int i = 0; i < mSessionSnapshotMap.size(); ++i) { final int uid = mSessionSnapshotMap.keyAt(i); final ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = mSessionSnapshotMap.valueAt(i); for (int j = 0; j < sessionSnapshots.size(); ++j) { final int sessionTag = sessionSnapshots.keyAt(j); final AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.valueAt(j); data.add(FrameworkStatsLog.buildStatsEvent( FrameworkStatsLog.ADPF_SESSION_SNAPSHOT, uid, sessionTag, sessionSnapshot.getMaxConcurrentSession(), sessionSnapshot.getMaxThreadCount(), sessionSnapshot.getPowerEfficientSessionCount(), sessionSnapshot.targetDurationNsList() )); } } } restoreSessionSnapshot(); } } return android.app.StatsManager.PULL_SUCCESS; return android.app.StatsManager.PULL_SUCCESS; } } private int getFmqUsage() { if (mUsesFmq) { return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__SUPPORTED; } else if (mPowerHalVersion < 5) { return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__HAL_VERSION_NOT_MET; } else { return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__UNSUPPORTED; } } private void restoreSessionSnapshot() { // clean up snapshot map and rebuild with current active sessions synchronized (mSessionSnapshotMapLock) { mSessionSnapshotMap.clear(); synchronized (mLock) { for (int i = 0; i < mActiveSessions.size(); i++) { ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.valueAt(i); for (int j = 0; j < tokenMap.size(); j++) { ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j); for (int k = 0; k < sessionSet.size(); ++k) { AppHintSession appHintSession = sessionSet.valueAt(k); final int tag = appHintSession.getTag(); final int uid = appHintSession.getUid(); final long targetDuationNs = appHintSession.getTargetDurationNs(); final int threadCount = appHintSession.getThreadIds().length; ArrayMap<Integer, AppHintSessionSnapshot> snapshots = mSessionSnapshotMap.get(uid); if (snapshots == null) { snapshots = new ArrayMap<>(); mSessionSnapshotMap.put(uid, snapshots); } AppHintSessionSnapshot snapshot = snapshots.get(tag); if (snapshot == null) { snapshot = new AppHintSessionSnapshot(); snapshots.put(tag, snapshot); } snapshot.updateUponSessionCreation(threadCount, targetDuationNs); } } } } } } /** /** * Wrapper around the static-native methods from native. * Wrapper around the static-native methods from native. * * Loading Loading @@ -833,17 +1061,13 @@ public final class HintManagerService extends SystemService { // we change the session tag to SessionTag.GAME // we change the session tag to SessionTag.GAME // as it was not previously classified // as it was not previously classified switch (getUidApplicationCategory(callingUid)) { switch (getUidApplicationCategory(callingUid)) { case ApplicationInfo.CATEGORY_GAME: case ApplicationInfo.CATEGORY_GAME -> tag = SessionTag.GAME; tag = SessionTag.GAME; case ApplicationInfo.CATEGORY_UNDEFINED -> break; case ApplicationInfo.CATEGORY_UNDEFINED: // We use CATEGORY_UNDEFINED to filter the case when // We use CATEGORY_UNDEFINED to filter the case when // PackageManager.NameNotFoundException is caught, // PackageManager.NameNotFoundException is caught, // which should not happen. // which should not happen. tag = SessionTag.APP; tag = SessionTag.APP; break; default -> tag = SessionTag.APP; default: tag = SessionTag.APP; } } } } Loading Loading @@ -889,9 +1113,15 @@ public final class HintManagerService extends SystemService { logPerformanceHintSessionAtom( logPerformanceHintSessionAtom( callingUid, sessionId, durationNanos, tids, tag); callingUid, sessionId, durationNanos, tids, tag); synchronized (mSessionSnapshotMapLock) { // Update session snapshot upon session creation mSessionSnapshotMap.computeIfAbsent(callingUid, k -> new ArrayMap<>()) .computeIfAbsent(tag, k -> new AppHintSessionSnapshot()) .updateUponSessionCreation(tids.length, durationNanos); } synchronized (mLock) { synchronized (mLock) { AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token, AppHintSession hs = new AppHintSession(callingUid, callingTgid, tag, tids, halSessionPtr, durationNanos); token, halSessionPtr, durationNanos); ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(callingUid); mActiveSessions.get(callingUid); if (tokenMap == null) { if (tokenMap == null) { Loading @@ -904,6 +1134,8 @@ public final class HintManagerService extends SystemService { tokenMap.put(token, sessionSet); tokenMap.put(token, sessionSet); } } sessionSet.add(hs); sessionSet.add(hs); mUsesFmq = mUsesFmq || hasChannel(callingTgid, callingUid); return hs; return hs; } } } finally { } finally { Loading Loading @@ -996,6 +1228,7 @@ public final class HintManagerService extends SystemService { final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient { final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient { protected final int mUid; protected final int mUid; protected final int mPid; protected final int mPid; protected final int mTag; protected int[] mThreadIds; protected int[] mThreadIds; protected final IBinder mToken; protected final IBinder mToken; protected long mHalSessionPtr; protected long mHalSessionPtr; Loading @@ -1003,6 +1236,7 @@ public final class HintManagerService extends SystemService { protected boolean mUpdateAllowedByProcState; protected boolean mUpdateAllowedByProcState; protected int[] mNewThreadIds; protected int[] mNewThreadIds; protected boolean mPowerEfficient; protected boolean mPowerEfficient; protected boolean mHasBeenPowerEfficient; protected boolean mShouldForcePause; protected boolean mShouldForcePause; private enum SessionModes { private enum SessionModes { Loading @@ -1010,16 +1244,18 @@ public final class HintManagerService extends SystemService { }; }; protected AppHintSession( protected AppHintSession( int uid, int pid, int[] threadIds, IBinder token, int uid, int pid, int sessionTag, int[] threadIds, IBinder token, long halSessionPtr, long durationNanos) { long halSessionPtr, long durationNanos) { mUid = uid; mUid = uid; mPid = pid; mPid = pid; mTag = sessionTag; mToken = token; mToken = token; mThreadIds = threadIds; mThreadIds = threadIds; mHalSessionPtr = halSessionPtr; mHalSessionPtr = halSessionPtr; mTargetDurationNanos = durationNanos; mTargetDurationNanos = durationNanos; mUpdateAllowedByProcState = true; mUpdateAllowedByProcState = true; mPowerEfficient = false; mPowerEfficient = false; mHasBeenPowerEfficient = false; mShouldForcePause = false; mShouldForcePause = false; final boolean allowed = mUidObserver.isUidForeground(mUid); final boolean allowed = mUidObserver.isUidForeground(mUid); updateHintAllowedByProcState(allowed); updateHintAllowedByProcState(allowed); Loading Loading @@ -1056,6 +1292,20 @@ public final class HintManagerService extends SystemService { mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos); mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos); mTargetDurationNanos = targetDurationNanos; mTargetDurationNanos = targetDurationNanos; } } synchronized (mSessionSnapshotMapLock) { ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = mSessionSnapshotMap.get(mUid); if (sessionSnapshots == null) { Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); return; } AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); if (sessionSnapshot == null) { Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag); return; } sessionSnapshot.updateTargetDurationNs(mTargetDurationNanos); } } } @Override @Override Loading Loading @@ -1108,6 +1358,20 @@ public final class HintManagerService extends SystemService { if (sessionSet.isEmpty()) tokenMap.remove(mToken); if (sessionSet.isEmpty()) tokenMap.remove(mToken); if (tokenMap.isEmpty()) mActiveSessions.remove(mUid); if (tokenMap.isEmpty()) mActiveSessions.remove(mUid); } } synchronized (mSessionSnapshotMapLock) { ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = mSessionSnapshotMap.get(mUid); if (sessionSnapshots == null) { Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); return; } AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); if (sessionSnapshot == null) { Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag); return; } sessionSnapshot.updateUponSessionClose(); } if (powerhintThreadCleanup()) { if (powerhintThreadCleanup()) { synchronized (mNonIsolatedTidsLock) { synchronized (mNonIsolatedTidsLock) { final int[] tids = getTidsInternal(); final int[] tids = getTidsInternal(); Loading Loading @@ -1191,6 +1455,21 @@ public final class HintManagerService extends SystemService { mShouldForcePause = false; mShouldForcePause = false; } } } } synchronized (mSessionSnapshotMapLock) { ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = mSessionSnapshotMap.get(mUid); if (sessionSnapshots == null) { Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); return; } AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); if (sessionSnapshot == null) { Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag); return; } sessionSnapshot.updateThreadCount(tids.length); } } } public int[] getThreadIds() { public int[] getThreadIds() { Loading Loading @@ -1231,6 +1510,26 @@ public final class HintManagerService extends SystemService { } } mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled); mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled); } } if (enabled && (mode == SessionModes.POWER_EFFICIENCY.ordinal())) { if (!mHasBeenPowerEfficient) { mHasBeenPowerEfficient = true; synchronized (mSessionSnapshotMapLock) { ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = mSessionSnapshotMap.get(mUid); if (sessionSnapshots == null) { Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); return; } AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); if (sessionSnapshot == null) { Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag); return; } sessionSnapshot.logPowerEfficientSession(); } } } } } @Override @Override Loading @@ -1254,6 +1553,20 @@ public final class HintManagerService extends SystemService { } } } } public int getUid() { return mUid; } public int getTag() { return mTag; } public long getTargetDurationNs() { synchronized (this) { return mTargetDurationNanos; } } void validateWorkDuration(WorkDuration workDuration) { void validateWorkDuration(WorkDuration workDuration) { if (DEBUG) { if (DEBUG) { Slogf.d(TAG, "WorkDuration(" Slogf.d(TAG, "WorkDuration(" Loading services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +3 −4 Original line number Original line Diff line number Diff line Loading @@ -151,7 +151,6 @@ public class HintManagerServiceTest { private HintManagerService mService; private HintManagerService mService; private ChannelConfig mConfig; private ChannelConfig mConfig; private ApplicationInfo mApplicationInfo; private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { return new Answer<Long>() { return new Answer<Long>() { Loading @@ -168,12 +167,12 @@ public class HintManagerServiceTest { mConfig = new ChannelConfig(); mConfig = new ChannelConfig(); mConfig.readFlagBitmask = 1; mConfig.readFlagBitmask = 1; mConfig.writeFlagBitmask = 2; mConfig.writeFlagBitmask = 2; mApplicationInfo = new ApplicationInfo(); ApplicationInfo applicationInfo = new ApplicationInfo(); mApplicationInfo.category = ApplicationInfo.CATEGORY_GAME; applicationInfo.category = ApplicationInfo.CATEGORY_GAME; when(mContext.getPackageManager()).thenReturn(mMockPackageManager); when(mContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME); when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME); when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt())) when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt())) .thenReturn(mApplicationInfo); .thenReturn(applicationInfo); when(mNativeWrapperMock.halGetHintSessionPreferredRate()) when(mNativeWrapperMock.halGetHintSessionPreferredRate()) .thenReturn(DEFAULT_HINT_PREFERRED_RATE); .thenReturn(DEFAULT_HINT_PREFERRED_RATE); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), Loading Loading
services/core/java/com/android/server/power/hint/HintManagerService.java +324 −11 Original line number Original line Diff line number Diff line Loading @@ -76,6 +76,7 @@ import java.util.List; import java.util.Map; import java.util.Map; import java.util.NoSuchElementException; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; import java.util.Set; import java.util.TreeMap; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit; Loading Loading @@ -109,12 +110,31 @@ public final class HintManagerService extends SystemService { @GuardedBy("mChannelMapLock") @GuardedBy("mChannelMapLock") private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap; private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap; /* * Multi-level map storing the session statistics since last pull from StatsD. * The first level is keyed by the UID of the process owning the session. * The second level is keyed by the tag of the session. The point of separating different * tags is that since different categories (e.g. HWUI vs APP) of the sessions may have different * behaviors. */ @GuardedBy("mSessionSnapshotMapLock") private ArrayMap<Integer, ArrayMap<Integer, AppHintSessionSnapshot>> mSessionSnapshotMap; /** Lock to protect mActiveSessions and the UidObserver. */ /** Lock to protect mActiveSessions and the UidObserver. */ private final Object mLock = new Object(); private final Object mLock = new Object(); /** Lock to protect mChannelMap. */ /** Lock to protect mChannelMap. */ private final Object mChannelMapLock = new Object(); private final Object mChannelMapLock = new Object(); /* * Lock to protect mSessionSnapshotMap. * Nested acquisition of mSessionSnapshotMapLock and mLock should be avoided. * We should grab these separately. * When we need to have nested acquisitions, we should always follow the order of acquiring * mSessionSnapshotMapLock first then mLock. */ private final Object mSessionSnapshotMapLock = new Object(); @GuardedBy("mNonIsolatedTidsLock") @GuardedBy("mNonIsolatedTidsLock") private final Map<Integer, Set<Long>> mNonIsolatedTids; private final Map<Integer, Set<Long>> mNonIsolatedTids; Loading @@ -135,6 +155,8 @@ public final class HintManagerService extends SystemService { private int mPowerHalVersion; private int mPowerHalVersion; private final PackageManager mPackageManager; private final PackageManager mPackageManager; private boolean mUsesFmq; private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint"; 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"; private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; Loading Loading @@ -162,6 +184,7 @@ public final class HintManagerService extends SystemService { } } mActiveSessions = new ArrayMap<>(); mActiveSessions = new ArrayMap<>(); mChannelMap = new ArrayMap<>(); mChannelMap = new ArrayMap<>(); mSessionSnapshotMap = new ArrayMap<>(); mNativeWrapper = injector.createNativeWrapper(); mNativeWrapper = injector.createNativeWrapper(); mNativeWrapper.halInit(); mNativeWrapper.halInit(); mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); Loading @@ -170,6 +193,7 @@ public final class HintManagerService extends SystemService { LocalServices.getService(ActivityManagerInternal.class)); LocalServices.getService(ActivityManagerInternal.class)); mPowerHal = injector.createIPower(); mPowerHal = injector.createIPower(); mPowerHalVersion = 0; mPowerHalVersion = 0; mUsesFmq = false; if (mPowerHal != null) { if (mPowerHal != null) { try { try { mPowerHalVersion = mPowerHal.getInterfaceVersion(); mPowerHalVersion = mPowerHal.getInterfaceVersion(); Loading Loading @@ -197,6 +221,134 @@ public final class HintManagerService extends SystemService { } } } } private class AppHintSessionSnapshot { /* * Per-Uid and Per-SessionTag snapshot that tracks metrics including * number of created sessions, number of power efficienct sessions, and * maximum number of threads in a session. * Given that it's Per-SessionTag, each uid can have multiple snapshots. */ int mCurrentSessionCount; int mMaxConcurrentSession; int mMaxThreadCount; int mPowerEfficientSessionCount; final int mTargetDurationNsCountPQSize = 100; PriorityQueue<TargetDurationRecord> mTargetDurationNsCountPQ; class TargetDurationRecord implements Comparable<TargetDurationRecord> { long mTargetDurationNs; long mTimestamp; int mCount; TargetDurationRecord(long targetDurationNs) { mTargetDurationNs = targetDurationNs; mTimestamp = System.currentTimeMillis(); mCount = 1; } @Override public int compareTo(TargetDurationRecord t) { int tCount = t.getCount(); int thisCount = this.getCount(); // Here we sort in the order of number of count in ascending order. // i.e. the lowest count of target duration is at the head of the queue. // Upon same count, the tiebreaker is the timestamp, the older item will be at the // front of the queue. if (tCount == thisCount) { return (t.getTimestamp() < this.getTimestamp()) ? 1 : -1; } return (tCount < thisCount) ? 1 : -1; } long getTargetDurationNs() { return mTargetDurationNs; } int getCount() { return mCount; } long getTimestamp() { return mTimestamp; } void setCount(int count) { mCount = count; } void setTimestamp() { mTimestamp = System.currentTimeMillis(); } void setTargetDurationNs(long targetDurationNs) { mTargetDurationNs = targetDurationNs; } } AppHintSessionSnapshot() { mCurrentSessionCount = 0; mMaxConcurrentSession = 0; mMaxThreadCount = 0; mPowerEfficientSessionCount = 0; mTargetDurationNsCountPQ = new PriorityQueue<>(1); } void updateUponSessionCreation(int threadCount, long targetDuration) { mCurrentSessionCount += 1; mMaxConcurrentSession = Math.max(mMaxConcurrentSession, mCurrentSessionCount); mMaxThreadCount = Math.max(mMaxThreadCount, threadCount); updateTargetDurationNs(targetDuration); } void updateUponSessionClose() { mCurrentSessionCount -= 1; } void logPowerEfficientSession() { mPowerEfficientSessionCount += 1; } void updateThreadCount(int threadCount) { mMaxThreadCount = Math.max(mMaxThreadCount, threadCount); } void updateTargetDurationNs(long targetDurationNs) { for (TargetDurationRecord t : mTargetDurationNsCountPQ) { if (t.getTargetDurationNs() == targetDurationNs) { t.setCount(t.getCount() + 1); t.setTimestamp(); return; } } if (mTargetDurationNsCountPQ.size() == mTargetDurationNsCountPQSize) { mTargetDurationNsCountPQ.poll(); } mTargetDurationNsCountPQ.add(new TargetDurationRecord(targetDurationNs)); } int getMaxConcurrentSession() { return mMaxConcurrentSession; } int getMaxThreadCount() { return mMaxThreadCount; } int getPowerEfficientSessionCount() { return mPowerEfficientSessionCount; } long[] targetDurationNsList() { final int listSize = 5; long[] targetDurations = new long[listSize]; while (mTargetDurationNsCountPQ.size() > listSize) { mTargetDurationNsCountPQ.poll(); } for (int i = 0; i < listSize && !mTargetDurationNsCountPQ.isEmpty(); ++i) { targetDurations[i] = mTargetDurationNsCountPQ.poll().getTargetDurationNs(); } return targetDurations; } } private boolean isHalSupported() { private boolean isHalSupported() { return mHintSessionPreferredRate != -1; return mHintSessionPreferredRate != -1; } } Loading Loading @@ -235,6 +387,11 @@ public final class HintManagerService extends SystemService { null, // use default PullAtomMetadata values null, // use default PullAtomMetadata values DIRECT_EXECUTOR, DIRECT_EXECUTOR, this::onPullAtom); this::onPullAtom); statsManager.setPullAtomCallback( FrameworkStatsLog.ADPF_SESSION_SNAPSHOT, null, // use default PullAtomMetadata values DIRECT_EXECUTOR, this::onPullAtom); } } private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) { private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) { Loading @@ -247,11 +404,82 @@ public final class HintManagerService extends SystemService { data.add(FrameworkStatsLog.buildStatsEvent( data.add(FrameworkStatsLog.buildStatsEvent( FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO, FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO, isSurfaceFlingerUsingCpuHint, isSurfaceFlingerUsingCpuHint, isHwuiHintManagerEnabled)); isHwuiHintManagerEnabled, getFmqUsage())); } if (atomTag == FrameworkStatsLog.ADPF_SESSION_SNAPSHOT) { synchronized (mSessionSnapshotMapLock) { for (int i = 0; i < mSessionSnapshotMap.size(); ++i) { final int uid = mSessionSnapshotMap.keyAt(i); final ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = mSessionSnapshotMap.valueAt(i); for (int j = 0; j < sessionSnapshots.size(); ++j) { final int sessionTag = sessionSnapshots.keyAt(j); final AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.valueAt(j); data.add(FrameworkStatsLog.buildStatsEvent( FrameworkStatsLog.ADPF_SESSION_SNAPSHOT, uid, sessionTag, sessionSnapshot.getMaxConcurrentSession(), sessionSnapshot.getMaxThreadCount(), sessionSnapshot.getPowerEfficientSessionCount(), sessionSnapshot.targetDurationNsList() )); } } } restoreSessionSnapshot(); } } return android.app.StatsManager.PULL_SUCCESS; return android.app.StatsManager.PULL_SUCCESS; } } private int getFmqUsage() { if (mUsesFmq) { return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__SUPPORTED; } else if (mPowerHalVersion < 5) { return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__HAL_VERSION_NOT_MET; } else { return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__UNSUPPORTED; } } private void restoreSessionSnapshot() { // clean up snapshot map and rebuild with current active sessions synchronized (mSessionSnapshotMapLock) { mSessionSnapshotMap.clear(); synchronized (mLock) { for (int i = 0; i < mActiveSessions.size(); i++) { ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.valueAt(i); for (int j = 0; j < tokenMap.size(); j++) { ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j); for (int k = 0; k < sessionSet.size(); ++k) { AppHintSession appHintSession = sessionSet.valueAt(k); final int tag = appHintSession.getTag(); final int uid = appHintSession.getUid(); final long targetDuationNs = appHintSession.getTargetDurationNs(); final int threadCount = appHintSession.getThreadIds().length; ArrayMap<Integer, AppHintSessionSnapshot> snapshots = mSessionSnapshotMap.get(uid); if (snapshots == null) { snapshots = new ArrayMap<>(); mSessionSnapshotMap.put(uid, snapshots); } AppHintSessionSnapshot snapshot = snapshots.get(tag); if (snapshot == null) { snapshot = new AppHintSessionSnapshot(); snapshots.put(tag, snapshot); } snapshot.updateUponSessionCreation(threadCount, targetDuationNs); } } } } } } /** /** * Wrapper around the static-native methods from native. * Wrapper around the static-native methods from native. * * Loading Loading @@ -833,17 +1061,13 @@ public final class HintManagerService extends SystemService { // we change the session tag to SessionTag.GAME // we change the session tag to SessionTag.GAME // as it was not previously classified // as it was not previously classified switch (getUidApplicationCategory(callingUid)) { switch (getUidApplicationCategory(callingUid)) { case ApplicationInfo.CATEGORY_GAME: case ApplicationInfo.CATEGORY_GAME -> tag = SessionTag.GAME; tag = SessionTag.GAME; case ApplicationInfo.CATEGORY_UNDEFINED -> break; case ApplicationInfo.CATEGORY_UNDEFINED: // We use CATEGORY_UNDEFINED to filter the case when // We use CATEGORY_UNDEFINED to filter the case when // PackageManager.NameNotFoundException is caught, // PackageManager.NameNotFoundException is caught, // which should not happen. // which should not happen. tag = SessionTag.APP; tag = SessionTag.APP; break; default -> tag = SessionTag.APP; default: tag = SessionTag.APP; } } } } Loading Loading @@ -889,9 +1113,15 @@ public final class HintManagerService extends SystemService { logPerformanceHintSessionAtom( logPerformanceHintSessionAtom( callingUid, sessionId, durationNanos, tids, tag); callingUid, sessionId, durationNanos, tids, tag); synchronized (mSessionSnapshotMapLock) { // Update session snapshot upon session creation mSessionSnapshotMap.computeIfAbsent(callingUid, k -> new ArrayMap<>()) .computeIfAbsent(tag, k -> new AppHintSessionSnapshot()) .updateUponSessionCreation(tids.length, durationNanos); } synchronized (mLock) { synchronized (mLock) { AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token, AppHintSession hs = new AppHintSession(callingUid, callingTgid, tag, tids, halSessionPtr, durationNanos); token, halSessionPtr, durationNanos); ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(callingUid); mActiveSessions.get(callingUid); if (tokenMap == null) { if (tokenMap == null) { Loading @@ -904,6 +1134,8 @@ public final class HintManagerService extends SystemService { tokenMap.put(token, sessionSet); tokenMap.put(token, sessionSet); } } sessionSet.add(hs); sessionSet.add(hs); mUsesFmq = mUsesFmq || hasChannel(callingTgid, callingUid); return hs; return hs; } } } finally { } finally { Loading Loading @@ -996,6 +1228,7 @@ public final class HintManagerService extends SystemService { final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient { final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient { protected final int mUid; protected final int mUid; protected final int mPid; protected final int mPid; protected final int mTag; protected int[] mThreadIds; protected int[] mThreadIds; protected final IBinder mToken; protected final IBinder mToken; protected long mHalSessionPtr; protected long mHalSessionPtr; Loading @@ -1003,6 +1236,7 @@ public final class HintManagerService extends SystemService { protected boolean mUpdateAllowedByProcState; protected boolean mUpdateAllowedByProcState; protected int[] mNewThreadIds; protected int[] mNewThreadIds; protected boolean mPowerEfficient; protected boolean mPowerEfficient; protected boolean mHasBeenPowerEfficient; protected boolean mShouldForcePause; protected boolean mShouldForcePause; private enum SessionModes { private enum SessionModes { Loading @@ -1010,16 +1244,18 @@ public final class HintManagerService extends SystemService { }; }; protected AppHintSession( protected AppHintSession( int uid, int pid, int[] threadIds, IBinder token, int uid, int pid, int sessionTag, int[] threadIds, IBinder token, long halSessionPtr, long durationNanos) { long halSessionPtr, long durationNanos) { mUid = uid; mUid = uid; mPid = pid; mPid = pid; mTag = sessionTag; mToken = token; mToken = token; mThreadIds = threadIds; mThreadIds = threadIds; mHalSessionPtr = halSessionPtr; mHalSessionPtr = halSessionPtr; mTargetDurationNanos = durationNanos; mTargetDurationNanos = durationNanos; mUpdateAllowedByProcState = true; mUpdateAllowedByProcState = true; mPowerEfficient = false; mPowerEfficient = false; mHasBeenPowerEfficient = false; mShouldForcePause = false; mShouldForcePause = false; final boolean allowed = mUidObserver.isUidForeground(mUid); final boolean allowed = mUidObserver.isUidForeground(mUid); updateHintAllowedByProcState(allowed); updateHintAllowedByProcState(allowed); Loading Loading @@ -1056,6 +1292,20 @@ public final class HintManagerService extends SystemService { mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos); mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos); mTargetDurationNanos = targetDurationNanos; mTargetDurationNanos = targetDurationNanos; } } synchronized (mSessionSnapshotMapLock) { ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = mSessionSnapshotMap.get(mUid); if (sessionSnapshots == null) { Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); return; } AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); if (sessionSnapshot == null) { Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag); return; } sessionSnapshot.updateTargetDurationNs(mTargetDurationNanos); } } } @Override @Override Loading Loading @@ -1108,6 +1358,20 @@ public final class HintManagerService extends SystemService { if (sessionSet.isEmpty()) tokenMap.remove(mToken); if (sessionSet.isEmpty()) tokenMap.remove(mToken); if (tokenMap.isEmpty()) mActiveSessions.remove(mUid); if (tokenMap.isEmpty()) mActiveSessions.remove(mUid); } } synchronized (mSessionSnapshotMapLock) { ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = mSessionSnapshotMap.get(mUid); if (sessionSnapshots == null) { Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); return; } AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); if (sessionSnapshot == null) { Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag); return; } sessionSnapshot.updateUponSessionClose(); } if (powerhintThreadCleanup()) { if (powerhintThreadCleanup()) { synchronized (mNonIsolatedTidsLock) { synchronized (mNonIsolatedTidsLock) { final int[] tids = getTidsInternal(); final int[] tids = getTidsInternal(); Loading Loading @@ -1191,6 +1455,21 @@ public final class HintManagerService extends SystemService { mShouldForcePause = false; mShouldForcePause = false; } } } } synchronized (mSessionSnapshotMapLock) { ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = mSessionSnapshotMap.get(mUid); if (sessionSnapshots == null) { Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); return; } AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); if (sessionSnapshot == null) { Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag); return; } sessionSnapshot.updateThreadCount(tids.length); } } } public int[] getThreadIds() { public int[] getThreadIds() { Loading Loading @@ -1231,6 +1510,26 @@ public final class HintManagerService extends SystemService { } } mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled); mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled); } } if (enabled && (mode == SessionModes.POWER_EFFICIENCY.ordinal())) { if (!mHasBeenPowerEfficient) { mHasBeenPowerEfficient = true; synchronized (mSessionSnapshotMapLock) { ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = mSessionSnapshotMap.get(mUid); if (sessionSnapshots == null) { Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); return; } AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); if (sessionSnapshot == null) { Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag); return; } sessionSnapshot.logPowerEfficientSession(); } } } } } @Override @Override Loading @@ -1254,6 +1553,20 @@ public final class HintManagerService extends SystemService { } } } } public int getUid() { return mUid; } public int getTag() { return mTag; } public long getTargetDurationNs() { synchronized (this) { return mTargetDurationNanos; } } void validateWorkDuration(WorkDuration workDuration) { void validateWorkDuration(WorkDuration workDuration) { if (DEBUG) { if (DEBUG) { Slogf.d(TAG, "WorkDuration(" Slogf.d(TAG, "WorkDuration(" Loading
services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +3 −4 Original line number Original line Diff line number Diff line Loading @@ -151,7 +151,6 @@ public class HintManagerServiceTest { private HintManagerService mService; private HintManagerService mService; private ChannelConfig mConfig; private ChannelConfig mConfig; private ApplicationInfo mApplicationInfo; private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { return new Answer<Long>() { return new Answer<Long>() { Loading @@ -168,12 +167,12 @@ public class HintManagerServiceTest { mConfig = new ChannelConfig(); mConfig = new ChannelConfig(); mConfig.readFlagBitmask = 1; mConfig.readFlagBitmask = 1; mConfig.writeFlagBitmask = 2; mConfig.writeFlagBitmask = 2; mApplicationInfo = new ApplicationInfo(); ApplicationInfo applicationInfo = new ApplicationInfo(); mApplicationInfo.category = ApplicationInfo.CATEGORY_GAME; applicationInfo.category = ApplicationInfo.CATEGORY_GAME; when(mContext.getPackageManager()).thenReturn(mMockPackageManager); when(mContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME); when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME); when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt())) when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt())) .thenReturn(mApplicationInfo); .thenReturn(applicationInfo); when(mNativeWrapperMock.halGetHintSessionPreferredRate()) when(mNativeWrapperMock.halGetHintSessionPreferredRate()) .thenReturn(DEFAULT_HINT_PREFERRED_RATE); .thenReturn(DEFAULT_HINT_PREFERRED_RATE); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), Loading