Loading services/core/java/com/android/server/am/CachedAppOptimizer.java +51 −22 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import com.android.server.ServiceThread; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; Loading Loading @@ -114,6 +115,14 @@ public final class CachedAppOptimizer { } private PropertyChangedCallbackForTest mTestCallback; // This interface is for functions related to the Process object that need a different // implementation in the tests as we are not creating real processes when testing compaction. @VisibleForTesting interface ProcessDependencies { long[] getRss(int pid); void performCompaction(String action, int pid) throws IOException; } // Handler constants. static final int COMPACT_PROCESS_SOME = 1; static final int COMPACT_PROCESS_FULL = 2; Loading Loading @@ -215,13 +224,16 @@ public final class CachedAppOptimizer { @VisibleForTesting final Set<Integer> mProcStateThrottle; // Handler on which compaction runs. private Handler mCompactionHandler; @VisibleForTesting Handler mCompactionHandler; private Handler mFreezeHandler; // Maps process ID to last compaction statistics for processes that we've fully compacted. Used // when evaluating throttles that we only consider for "full" compaction, so we don't store // data for "some" compactions. private Map<Integer, LastCompactionStats> mLastCompactionStats = // data for "some" compactions. Uses LinkedHashMap to ensure insertion order is kept and // facilitate removal of the oldest entry. @VisibleForTesting LinkedHashMap<Integer, LastCompactionStats> mLastCompactionStats = new LinkedHashMap<Integer, LastCompactionStats>() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { Loading @@ -233,17 +245,20 @@ public final class CachedAppOptimizer { private int mFullCompactionCount; private int mPersistentCompactionCount; private int mBfgsCompactionCount; private final ProcessDependencies mProcessDependencies; public CachedAppOptimizer(ActivityManagerService am) { this(am, null, new DefaultProcessDependencies()); } @VisibleForTesting CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback, ProcessDependencies processDependencies) { mAm = am; mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread", THREAD_PRIORITY_FOREGROUND, true); mProcStateThrottle = new HashSet<>(); } @VisibleForTesting CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) { this(am); mProcessDependencies = processDependencies; mTestCallback = callback; } Loading Loading @@ -659,7 +674,8 @@ public final class CachedAppOptimizer { } } private static final class LastCompactionStats { @VisibleForTesting static final class LastCompactionStats { private final long[] mRssAfterCompaction; LastCompactionStats(long[] rss) { Loading Loading @@ -712,9 +728,7 @@ public final class CachedAppOptimizer { lastCompactAction = proc.lastCompactAction; lastCompactTime = proc.lastCompactTime; // remove rather than get so that insertion order will be updated when we // put the post-compaction stats back into the map. lastCompactionStats = mLastCompactionStats.remove(pid); lastCompactionStats = mLastCompactionStats.get(pid); } if (pid == 0) { Loading Loading @@ -806,7 +820,7 @@ public final class CachedAppOptimizer { return; } long[] rssBefore = Process.getRss(pid); long[] rssBefore = mProcessDependencies.getRss(pid); long anonRssBefore = rssBefore[2]; if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0 Loading Loading @@ -863,16 +877,13 @@ public final class CachedAppOptimizer { default: break; } try { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + ": " + name); long zramFreeKbBefore = Debug.getZramFreeKb(); FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); fos.write(action.getBytes()); fos.close(); long[] rssAfter = Process.getRss(pid); mProcessDependencies.performCompaction(action, pid); long[] rssAfter = mProcessDependencies.getRss(pid); long end = SystemClock.uptimeMillis(); long time = end - start; long zramFreeKbAfter = Debug.getZramFreeKb(); Loading @@ -882,7 +893,6 @@ public final class CachedAppOptimizer { rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time, lastCompactAction, lastCompactTime, lastOomAdj, procState, zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore); // Note that as above not taking mPhenoTypeFlagLock here to avoid locking // on every single compaction for a flag that will seldom change and the // impact of reading the wrong value here is low. Loading @@ -894,14 +904,14 @@ public final class CachedAppOptimizer { lastOomAdj, ActivityManager.processStateAmToProto(procState), zramFreeKbBefore, zramFreeKbAfter); } synchronized (mAm) { proc.lastCompactTime = end; proc.lastCompactAction = pendingAction; } if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) { // Remove entry and insert again to update insertion order. mLastCompactionStats.remove(pid); mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter)); } } catch (Exception e) { Loading Loading @@ -1018,4 +1028,23 @@ public final class CachedAppOptimizer { } } } /** * Default implementation for ProcessDependencies, public vor visibility to OomAdjuster class. */ private static final class DefaultProcessDependencies implements ProcessDependencies { // Get memory RSS from process. @Override public long[] getRss(int pid) { return Process.getRss(pid); } // Compact process. @Override public void performCompaction(String action, int pid) throws IOException { try (FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim")) { fos.write(action.getBytes()); } } } } services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java +240 −9 Original line number Diff line number Diff line Loading @@ -16,14 +16,23 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static com.android.server.am.ActivityManagerService.Injector; import static com.android.server.am.CachedAppOptimizer.compactActionIntToString; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManagerInternal; import android.os.Handler; import android.os.HandlerThread; import android.os.MessageQueue; import android.os.Process; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; Loading @@ -31,9 +40,11 @@ import android.text.TextUtils; import androidx.test.platform.app.InstrumentationRegistry; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.appop.AppOpsService; import com.android.server.testables.TestableDeviceConfig; import com.android.server.wm.ActivityTaskManagerService; import org.junit.After; import org.junit.Assume; Loading @@ -45,6 +56,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; Loading @@ -68,25 +80,36 @@ public final class CachedAppOptimizerTest { private HandlerThread mHandlerThread; private Handler mHandler; private CountDownLatch mCountDown; private ActivityManagerService mAms; private Context mContext; private TestInjector mInjector; private TestProcessDependencies mProcessDependencies; @Mock private PackageManagerInternal mPackageManagerInt; @Rule public TestableDeviceConfig.TestableDeviceConfigRule public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule(); @Rule public final ApplicationExitInfoTest.ServiceThreadRule mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); @Before public void setUp() { mHandlerThread = new HandlerThread(""); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */); mThread.start(); ActivityManagerService ams = new ActivityManagerService( new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()), mThread); mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams, mContext = InstrumentationRegistry.getInstrumentation().getContext(); mInjector = new TestInjector(mContext); mAms = new ActivityManagerService( new TestInjector(mContext), mServiceThreadRule.getThread()); doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); mProcessDependencies = new TestProcessDependencies(); mCachedAppOptimizerUnderTest = new CachedAppOptimizer(mAms, new CachedAppOptimizer.PropertyChangedCallbackForTest() { @Override public void onPropertyChanged() { Loading @@ -94,7 +117,9 @@ public final class CachedAppOptimizerTest { mCountDown.countDown(); } } }); }, mProcessDependencies); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); } @After Loading @@ -104,6 +129,19 @@ public final class CachedAppOptimizerTest { mCountDown = null; } private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, String processName, String packageName) { ApplicationInfo ai = new ApplicationInfo(); ai.packageName = packageName; ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid); app.pid = pid; app.info.uid = packageUid; // Exact value does not mater, it can be any state for which compaction is allowed. app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; app.setAdj = 905; return app; } @Test public void init_setsDefaults() { mCachedAppOptimizerUnderTest.init(); Loading Loading @@ -790,6 +828,174 @@ public final class CachedAppOptimizerTest { .containsExactlyElementsIn(expected); } @Test public void processWithDeltaRSSTooSmall_notFullCompacted() throws Exception { // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS // throttle to 12000. mCachedAppOptimizerUnderTest.init(); setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true); setFlag(CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "12000", false); initActivityManagerService(); // Simulate RSS anon memory larger than throttle. long[] rssBefore1 = new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 10000, /*anonRSS*/ 12000, /*swap*/ 10000}; long[] rssAfter1 = new long[]{/*totalRSS*/ 9000, /*fileRSS*/ 9000, /*anonRSS*/ 11000, /*swap*/9000}; // Delta between rssAfter1 and rssBefore2 is below threshold (500). long[] rssBefore2 = new long[]{/*totalRSS*/ 9500, /*fileRSS*/ 9500, /*anonRSS*/ 11500, /*swap*/9500}; long[] rssAfter2 = new long[]{/*totalRSS*/ 8000, /*fileRSS*/ 8000, /*anonRSS*/ 9000, /*swap*/8000}; // Delta between rssAfter1 and rssBefore3 is above threshold (13000). long[] rssBefore3 = new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 18000, /*anonRSS*/ 13000, /*swap*/ 7000}; long[] rssAfter3 = new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 11000, /*anonRSS*/ 10000, /*swap*/ 6000}; long[] valuesAfter = {}; // Process that passes properties. int pid = 1; ProcessRecord processRecord = makeProcessRecord(pid, 2, 3, "p1", "app1"); // GIVEN we simulate RSS memory before above thresholds and it is the first time 'p1' is // compacted. mProcessDependencies.setRss(rssBefore1); mProcessDependencies.setRssAfterCompaction(rssAfter1); // // WHEN we try to run compaction mCachedAppOptimizerUnderTest.compactAppFull(processRecord); waitForHandler(); // THEN process IS compacted. assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( pid).getRssAfterCompaction(); assertThat(valuesAfter).isEqualTo(rssAfter1); // WHEN delta is below threshold (500). mProcessDependencies.setRss(rssBefore2); mProcessDependencies.setRssAfterCompaction(rssAfter2); // This is to avoid throttle of compacting too soon. processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000; // WHEN we try to run compaction. mCachedAppOptimizerUnderTest.compactAppFull(processRecord); waitForHandler(); // THEN process IS NOT compacted - values after compaction for process 1 should remain the // same as from the last compaction. assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( pid).getRssAfterCompaction(); assertThat(valuesAfter).isEqualTo(rssAfter1); // WHEN delta is above threshold (13000). mProcessDependencies.setRss(rssBefore3); mProcessDependencies.setRssAfterCompaction(rssAfter3); // This is to avoid throttle of compacting too soon. processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000; // WHEN we try to run compaction mCachedAppOptimizerUnderTest.compactAppFull(processRecord); waitForHandler(); // THEN process IS compacted - values after compaction for process 1 should be updated. assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( pid).getRssAfterCompaction(); assertThat(valuesAfter).isEqualTo(rssAfter3); } @Test public void processWithAnonRSSTooSmall_notFullCompacted() throws Exception { // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS // throttle to 8000. mCachedAppOptimizerUnderTest.init(); setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true); setFlag(CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "8000", false); initActivityManagerService(); // Simulate RSS anon memory larger than throttle. long[] rssBelowThreshold = new long[]{/*Total RSS*/ 10000, /*File RSS*/ 10000, /*Anon RSS*/ 7000, /*Swap*/ 10000}; long[] rssBelowThresholdAfter = new long[]{/*Total RSS*/ 9000, /*File RSS*/ 7000, /*Anon RSS*/ 4000, /*Swap*/ 8000}; long[] rssAboveThreshold = new long[]{/*Total RSS*/ 10000, /*File RSS*/ 10000, /*Anon RSS*/ 9000, /*Swap*/ 10000}; long[] rssAboveThresholdAfter = new long[]{/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/5000}; // Process that passes properties. int pid = 1; ProcessRecord processRecord = makeProcessRecord(pid, 2, 3, "p1", "app1"); // GIVEN we simulate RSS memory before below threshold. mProcessDependencies.setRss(rssBelowThreshold); mProcessDependencies.setRssAfterCompaction(rssBelowThresholdAfter); // WHEN we try to run compaction mCachedAppOptimizerUnderTest.compactAppFull(processRecord); waitForHandler(); // THEN process IS NOT compacted. assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull(); // GIVEN we simulate RSS memory before above threshold. mProcessDependencies.setRss(rssAboveThreshold); mProcessDependencies.setRssAfterCompaction(rssAboveThresholdAfter); // WHEN we try to run compaction mCachedAppOptimizerUnderTest.compactAppFull(processRecord); waitForHandler(); // THEN process IS compacted. assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); long[] valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( pid).getRssAfterCompaction(); assertThat(valuesAfter).isEqualTo(rssAboveThresholdAfter); } private void setFlag(String key, String value, boolean defaultValue) throws Exception { mCountDown = new CountDownLatch(1); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, key, value, defaultValue); assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); } private void waitForHandler() { Idle idle = new Idle(); mCachedAppOptimizerUnderTest.mCompactionHandler.getLooper().getQueue().addIdleHandler(idle); mCachedAppOptimizerUnderTest.mCompactionHandler.post(() -> { }); idle.waitForIdle(); } private void initActivityManagerService() { mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread()); mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal()); mAms.mPackageManagerInt = mPackageManagerInt; } private static final class Idle implements MessageQueue.IdleHandler { private boolean mIdle; @Override public boolean queueIdle() { synchronized (this) { mIdle = true; notifyAll(); } return false; } public synchronized void waitForIdle() { while (!mIdle) { try { // Wait with a timeout of 10s. wait(10000); } catch (InterruptedException e) { } } } } private class TestInjector extends Injector { TestInjector(Context context) { Loading @@ -806,4 +1012,29 @@ public final class CachedAppOptimizerTest { return mHandler; } } // Test implementation for ProcessDependencies. private static final class TestProcessDependencies implements CachedAppOptimizer.ProcessDependencies { private long[] mRss; private long[] mRssAfterCompaction; @Override public long[] getRss(int pid) { return mRss; } @Override public void performCompaction(String action, int pid) throws IOException { mRss = mRssAfterCompaction; } public void setRss(long[] newValues) { mRss = newValues; } public void setRssAfterCompaction(long[] newValues) { mRssAfterCompaction = newValues; } } } Loading
services/core/java/com/android/server/am/CachedAppOptimizer.java +51 −22 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import com.android.server.ServiceThread; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; Loading Loading @@ -114,6 +115,14 @@ public final class CachedAppOptimizer { } private PropertyChangedCallbackForTest mTestCallback; // This interface is for functions related to the Process object that need a different // implementation in the tests as we are not creating real processes when testing compaction. @VisibleForTesting interface ProcessDependencies { long[] getRss(int pid); void performCompaction(String action, int pid) throws IOException; } // Handler constants. static final int COMPACT_PROCESS_SOME = 1; static final int COMPACT_PROCESS_FULL = 2; Loading Loading @@ -215,13 +224,16 @@ public final class CachedAppOptimizer { @VisibleForTesting final Set<Integer> mProcStateThrottle; // Handler on which compaction runs. private Handler mCompactionHandler; @VisibleForTesting Handler mCompactionHandler; private Handler mFreezeHandler; // Maps process ID to last compaction statistics for processes that we've fully compacted. Used // when evaluating throttles that we only consider for "full" compaction, so we don't store // data for "some" compactions. private Map<Integer, LastCompactionStats> mLastCompactionStats = // data for "some" compactions. Uses LinkedHashMap to ensure insertion order is kept and // facilitate removal of the oldest entry. @VisibleForTesting LinkedHashMap<Integer, LastCompactionStats> mLastCompactionStats = new LinkedHashMap<Integer, LastCompactionStats>() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { Loading @@ -233,17 +245,20 @@ public final class CachedAppOptimizer { private int mFullCompactionCount; private int mPersistentCompactionCount; private int mBfgsCompactionCount; private final ProcessDependencies mProcessDependencies; public CachedAppOptimizer(ActivityManagerService am) { this(am, null, new DefaultProcessDependencies()); } @VisibleForTesting CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback, ProcessDependencies processDependencies) { mAm = am; mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread", THREAD_PRIORITY_FOREGROUND, true); mProcStateThrottle = new HashSet<>(); } @VisibleForTesting CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) { this(am); mProcessDependencies = processDependencies; mTestCallback = callback; } Loading Loading @@ -659,7 +674,8 @@ public final class CachedAppOptimizer { } } private static final class LastCompactionStats { @VisibleForTesting static final class LastCompactionStats { private final long[] mRssAfterCompaction; LastCompactionStats(long[] rss) { Loading Loading @@ -712,9 +728,7 @@ public final class CachedAppOptimizer { lastCompactAction = proc.lastCompactAction; lastCompactTime = proc.lastCompactTime; // remove rather than get so that insertion order will be updated when we // put the post-compaction stats back into the map. lastCompactionStats = mLastCompactionStats.remove(pid); lastCompactionStats = mLastCompactionStats.get(pid); } if (pid == 0) { Loading Loading @@ -806,7 +820,7 @@ public final class CachedAppOptimizer { return; } long[] rssBefore = Process.getRss(pid); long[] rssBefore = mProcessDependencies.getRss(pid); long anonRssBefore = rssBefore[2]; if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0 Loading Loading @@ -863,16 +877,13 @@ public final class CachedAppOptimizer { default: break; } try { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + ": " + name); long zramFreeKbBefore = Debug.getZramFreeKb(); FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); fos.write(action.getBytes()); fos.close(); long[] rssAfter = Process.getRss(pid); mProcessDependencies.performCompaction(action, pid); long[] rssAfter = mProcessDependencies.getRss(pid); long end = SystemClock.uptimeMillis(); long time = end - start; long zramFreeKbAfter = Debug.getZramFreeKb(); Loading @@ -882,7 +893,6 @@ public final class CachedAppOptimizer { rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time, lastCompactAction, lastCompactTime, lastOomAdj, procState, zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore); // Note that as above not taking mPhenoTypeFlagLock here to avoid locking // on every single compaction for a flag that will seldom change and the // impact of reading the wrong value here is low. Loading @@ -894,14 +904,14 @@ public final class CachedAppOptimizer { lastOomAdj, ActivityManager.processStateAmToProto(procState), zramFreeKbBefore, zramFreeKbAfter); } synchronized (mAm) { proc.lastCompactTime = end; proc.lastCompactAction = pendingAction; } if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) { // Remove entry and insert again to update insertion order. mLastCompactionStats.remove(pid); mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter)); } } catch (Exception e) { Loading Loading @@ -1018,4 +1028,23 @@ public final class CachedAppOptimizer { } } } /** * Default implementation for ProcessDependencies, public vor visibility to OomAdjuster class. */ private static final class DefaultProcessDependencies implements ProcessDependencies { // Get memory RSS from process. @Override public long[] getRss(int pid) { return Process.getRss(pid); } // Compact process. @Override public void performCompaction(String action, int pid) throws IOException { try (FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim")) { fos.write(action.getBytes()); } } } }
services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java +240 −9 Original line number Diff line number Diff line Loading @@ -16,14 +16,23 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static com.android.server.am.ActivityManagerService.Injector; import static com.android.server.am.CachedAppOptimizer.compactActionIntToString; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManagerInternal; import android.os.Handler; import android.os.HandlerThread; import android.os.MessageQueue; import android.os.Process; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; Loading @@ -31,9 +40,11 @@ import android.text.TextUtils; import androidx.test.platform.app.InstrumentationRegistry; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.appop.AppOpsService; import com.android.server.testables.TestableDeviceConfig; import com.android.server.wm.ActivityTaskManagerService; import org.junit.After; import org.junit.Assume; Loading @@ -45,6 +56,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; Loading @@ -68,25 +80,36 @@ public final class CachedAppOptimizerTest { private HandlerThread mHandlerThread; private Handler mHandler; private CountDownLatch mCountDown; private ActivityManagerService mAms; private Context mContext; private TestInjector mInjector; private TestProcessDependencies mProcessDependencies; @Mock private PackageManagerInternal mPackageManagerInt; @Rule public TestableDeviceConfig.TestableDeviceConfigRule public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule(); @Rule public final ApplicationExitInfoTest.ServiceThreadRule mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); @Before public void setUp() { mHandlerThread = new HandlerThread(""); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */); mThread.start(); ActivityManagerService ams = new ActivityManagerService( new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()), mThread); mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams, mContext = InstrumentationRegistry.getInstrumentation().getContext(); mInjector = new TestInjector(mContext); mAms = new ActivityManagerService( new TestInjector(mContext), mServiceThreadRule.getThread()); doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); mProcessDependencies = new TestProcessDependencies(); mCachedAppOptimizerUnderTest = new CachedAppOptimizer(mAms, new CachedAppOptimizer.PropertyChangedCallbackForTest() { @Override public void onPropertyChanged() { Loading @@ -94,7 +117,9 @@ public final class CachedAppOptimizerTest { mCountDown.countDown(); } } }); }, mProcessDependencies); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); } @After Loading @@ -104,6 +129,19 @@ public final class CachedAppOptimizerTest { mCountDown = null; } private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, String processName, String packageName) { ApplicationInfo ai = new ApplicationInfo(); ai.packageName = packageName; ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid); app.pid = pid; app.info.uid = packageUid; // Exact value does not mater, it can be any state for which compaction is allowed. app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; app.setAdj = 905; return app; } @Test public void init_setsDefaults() { mCachedAppOptimizerUnderTest.init(); Loading Loading @@ -790,6 +828,174 @@ public final class CachedAppOptimizerTest { .containsExactlyElementsIn(expected); } @Test public void processWithDeltaRSSTooSmall_notFullCompacted() throws Exception { // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS // throttle to 12000. mCachedAppOptimizerUnderTest.init(); setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true); setFlag(CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "12000", false); initActivityManagerService(); // Simulate RSS anon memory larger than throttle. long[] rssBefore1 = new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 10000, /*anonRSS*/ 12000, /*swap*/ 10000}; long[] rssAfter1 = new long[]{/*totalRSS*/ 9000, /*fileRSS*/ 9000, /*anonRSS*/ 11000, /*swap*/9000}; // Delta between rssAfter1 and rssBefore2 is below threshold (500). long[] rssBefore2 = new long[]{/*totalRSS*/ 9500, /*fileRSS*/ 9500, /*anonRSS*/ 11500, /*swap*/9500}; long[] rssAfter2 = new long[]{/*totalRSS*/ 8000, /*fileRSS*/ 8000, /*anonRSS*/ 9000, /*swap*/8000}; // Delta between rssAfter1 and rssBefore3 is above threshold (13000). long[] rssBefore3 = new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 18000, /*anonRSS*/ 13000, /*swap*/ 7000}; long[] rssAfter3 = new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 11000, /*anonRSS*/ 10000, /*swap*/ 6000}; long[] valuesAfter = {}; // Process that passes properties. int pid = 1; ProcessRecord processRecord = makeProcessRecord(pid, 2, 3, "p1", "app1"); // GIVEN we simulate RSS memory before above thresholds and it is the first time 'p1' is // compacted. mProcessDependencies.setRss(rssBefore1); mProcessDependencies.setRssAfterCompaction(rssAfter1); // // WHEN we try to run compaction mCachedAppOptimizerUnderTest.compactAppFull(processRecord); waitForHandler(); // THEN process IS compacted. assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( pid).getRssAfterCompaction(); assertThat(valuesAfter).isEqualTo(rssAfter1); // WHEN delta is below threshold (500). mProcessDependencies.setRss(rssBefore2); mProcessDependencies.setRssAfterCompaction(rssAfter2); // This is to avoid throttle of compacting too soon. processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000; // WHEN we try to run compaction. mCachedAppOptimizerUnderTest.compactAppFull(processRecord); waitForHandler(); // THEN process IS NOT compacted - values after compaction for process 1 should remain the // same as from the last compaction. assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( pid).getRssAfterCompaction(); assertThat(valuesAfter).isEqualTo(rssAfter1); // WHEN delta is above threshold (13000). mProcessDependencies.setRss(rssBefore3); mProcessDependencies.setRssAfterCompaction(rssAfter3); // This is to avoid throttle of compacting too soon. processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000; // WHEN we try to run compaction mCachedAppOptimizerUnderTest.compactAppFull(processRecord); waitForHandler(); // THEN process IS compacted - values after compaction for process 1 should be updated. assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( pid).getRssAfterCompaction(); assertThat(valuesAfter).isEqualTo(rssAfter3); } @Test public void processWithAnonRSSTooSmall_notFullCompacted() throws Exception { // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS // throttle to 8000. mCachedAppOptimizerUnderTest.init(); setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true); setFlag(CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "8000", false); initActivityManagerService(); // Simulate RSS anon memory larger than throttle. long[] rssBelowThreshold = new long[]{/*Total RSS*/ 10000, /*File RSS*/ 10000, /*Anon RSS*/ 7000, /*Swap*/ 10000}; long[] rssBelowThresholdAfter = new long[]{/*Total RSS*/ 9000, /*File RSS*/ 7000, /*Anon RSS*/ 4000, /*Swap*/ 8000}; long[] rssAboveThreshold = new long[]{/*Total RSS*/ 10000, /*File RSS*/ 10000, /*Anon RSS*/ 9000, /*Swap*/ 10000}; long[] rssAboveThresholdAfter = new long[]{/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/5000}; // Process that passes properties. int pid = 1; ProcessRecord processRecord = makeProcessRecord(pid, 2, 3, "p1", "app1"); // GIVEN we simulate RSS memory before below threshold. mProcessDependencies.setRss(rssBelowThreshold); mProcessDependencies.setRssAfterCompaction(rssBelowThresholdAfter); // WHEN we try to run compaction mCachedAppOptimizerUnderTest.compactAppFull(processRecord); waitForHandler(); // THEN process IS NOT compacted. assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull(); // GIVEN we simulate RSS memory before above threshold. mProcessDependencies.setRss(rssAboveThreshold); mProcessDependencies.setRssAfterCompaction(rssAboveThresholdAfter); // WHEN we try to run compaction mCachedAppOptimizerUnderTest.compactAppFull(processRecord); waitForHandler(); // THEN process IS compacted. assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull(); long[] valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get( pid).getRssAfterCompaction(); assertThat(valuesAfter).isEqualTo(rssAboveThresholdAfter); } private void setFlag(String key, String value, boolean defaultValue) throws Exception { mCountDown = new CountDownLatch(1); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, key, value, defaultValue); assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); } private void waitForHandler() { Idle idle = new Idle(); mCachedAppOptimizerUnderTest.mCompactionHandler.getLooper().getQueue().addIdleHandler(idle); mCachedAppOptimizerUnderTest.mCompactionHandler.post(() -> { }); idle.waitForIdle(); } private void initActivityManagerService() { mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread()); mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal()); mAms.mPackageManagerInt = mPackageManagerInt; } private static final class Idle implements MessageQueue.IdleHandler { private boolean mIdle; @Override public boolean queueIdle() { synchronized (this) { mIdle = true; notifyAll(); } return false; } public synchronized void waitForIdle() { while (!mIdle) { try { // Wait with a timeout of 10s. wait(10000); } catch (InterruptedException e) { } } } } private class TestInjector extends Injector { TestInjector(Context context) { Loading @@ -806,4 +1012,29 @@ public final class CachedAppOptimizerTest { return mHandler; } } // Test implementation for ProcessDependencies. private static final class TestProcessDependencies implements CachedAppOptimizer.ProcessDependencies { private long[] mRss; private long[] mRssAfterCompaction; @Override public long[] getRss(int pid) { return mRss; } @Override public void performCompaction(String action, int pid) throws IOException { mRss = mRssAfterCompaction; } public void setRss(long[] newValues) { mRss = newValues; } public void setRssAfterCompaction(long[] newValues) { mRssAfterCompaction = newValues; } } }