| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.am; |
| |
| import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.spy; |
| |
| import android.app.ActivityManager; |
| import android.app.IApplicationThread; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManagerInternal; |
| import android.os.Environment; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Process; |
| import android.provider.DeviceConfig; |
| |
| 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.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.junit.MockitoJUnitRunner; |
| |
| import java.io.File; |
| import java.time.Instant; |
| import java.time.LocalDate; |
| import java.time.ZoneOffset; |
| import java.time.temporal.ChronoUnit; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Tests for {@link CachedAppOptimizer}. |
| * |
| * Build/Install/Run: |
| * atest FrameworksMockingServicesTests:CacheOomRankerTest |
| */ |
| @SuppressWarnings("GuardedBy") // No tests are concurrent, so no need to test locking. |
| @RunWith(MockitoJUnitRunner.class) |
| public class CacheOomRankerTest { |
| private static final Instant NOW = LocalDate.of(2021, 1, 1).atStartOfDay( |
| ZoneOffset.UTC).toInstant(); |
| |
| @Mock |
| private AppOpsService mAppOpsService; |
| private Handler mHandler; |
| private ActivityManagerService mAms; |
| |
| @Mock |
| private PackageManagerInternal mPackageManagerInt; |
| |
| @Rule |
| public final TestableDeviceConfig.TestableDeviceConfigRule |
| mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule(); |
| @Rule |
| public final ApplicationExitInfoTest.ServiceThreadRule |
| mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); |
| |
| private int mNextPid = 10000; |
| private int mNextUid = 30000; |
| private int mNextPackageUid = 40000; |
| private int mNextPackageName = 1; |
| private Map<Integer, Long> mPidToRss; |
| |
| private TestExecutor mExecutor = new TestExecutor(); |
| private CacheOomRanker mCacheOomRanker; |
| |
| @Before |
| public void setUp() { |
| HandlerThread handlerThread = new HandlerThread(""); |
| handlerThread.start(); |
| mHandler = new Handler(handlerThread.getLooper()); |
| /* allowIo */ |
| ServiceThread thread = new ServiceThread("TestServiceThread", |
| Process.THREAD_PRIORITY_DEFAULT, |
| true /* allowIo */); |
| thread.start(); |
| Context context = InstrumentationRegistry.getInstrumentation().getContext(); |
| mAms = new ActivityManagerService( |
| new TestInjector(context), mServiceThreadRule.getThread()); |
| mAms.mActivityTaskManager = new ActivityTaskManagerService(context); |
| mAms.mActivityTaskManager.initialize(null, null, context.getMainLooper()); |
| mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal()); |
| mAms.mPackageManagerInt = mPackageManagerInt; |
| doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); |
| LocalServices.removeServiceForTest(PackageManagerInternal.class); |
| LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); |
| |
| mPidToRss = new HashMap<>(); |
| mCacheOomRanker = new CacheOomRanker( |
| mAms, |
| pid -> { |
| Long rss = mPidToRss.get(pid); |
| assertThat(rss).isNotNull(); |
| return new long[]{rss}; |
| } |
| ); |
| mCacheOomRanker.init(mExecutor); |
| } |
| |
| @Test |
| public void init_listensForConfigChanges() throws InterruptedException { |
| mExecutor.init(); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_USE_OOM_RE_RANKING, |
| Boolean.TRUE.toString(), true); |
| mExecutor.waitForLatch(); |
| assertThat(mCacheOomRanker.useOomReranking()).isTrue(); |
| mExecutor.init(); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_USE_OOM_RE_RANKING, Boolean.FALSE.toString(), false); |
| mExecutor.waitForLatch(); |
| assertThat(mCacheOomRanker.useOomReranking()).isFalse(); |
| |
| mExecutor.init(); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK, |
| Integer.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK + 2), |
| false); |
| mExecutor.waitForLatch(); |
| assertThat(mCacheOomRanker.getNumberToReRank()) |
| .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK + 2); |
| |
| mExecutor.init(); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, |
| Integer.toString(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1), |
| false); |
| mExecutor.waitForLatch(); |
| assertThat(mCacheOomRanker.mPreserveTopNApps) |
| .isEqualTo(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1); |
| |
| mExecutor.init(); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT, |
| Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f), |
| false); |
| mExecutor.waitForLatch(); |
| assertThat(mCacheOomRanker.mLruWeight) |
| .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f); |
| |
| mExecutor.init(); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_RSS_WEIGHT, |
| Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_RSS_WEIGHT - 0.1f), |
| false); |
| mExecutor.waitForLatch(); |
| assertThat(mCacheOomRanker.mRssWeight) |
| .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_RSS_WEIGHT - 0.1f); |
| |
| mExecutor.init(); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT, |
| Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_USES_WEIGHT + 0.2f), |
| false); |
| mExecutor.waitForLatch(); |
| assertThat(mCacheOomRanker.mUsesWeight) |
| .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_USES_WEIGHT + 0.2f); |
| } |
| |
| @Test |
| public void reRankLruCachedApps_lruImpactsOrdering() throws InterruptedException { |
| setConfig(/* numberToReRank= */ 5, |
| /* preserveTopNApps= */ 0, |
| /* useFrequentRss= */ true, |
| /* rssUpdateRateMs= */ 0, |
| /* usesWeight= */ 0.0f, |
| /* pssWeight= */ 0.0f, |
| /* lruWeight= */1.0f); |
| |
| ProcessList list = new ProcessList(); |
| ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); |
| ProcessRecord lastUsed40MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| processList.add(lastUsed40MinutesAgo); |
| ProcessRecord lastUsed42MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| processList.add(lastUsed42MinutesAgo); |
| ProcessRecord lastUsed60MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000); |
| processList.add(lastUsed60MinutesAgo); |
| ProcessRecord lastUsed15MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| processList.add(lastUsed15MinutesAgo); |
| ProcessRecord lastUsed17MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20); |
| processList.add(lastUsed17MinutesAgo); |
| // Only re-ranking 5 entries so this should stay in most recent position. |
| ProcessRecord lastUsed30MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20); |
| processList.add(lastUsed30MinutesAgo); |
| list.setLruProcessServiceStartLSP(processList.size()); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| |
| // First 5 ordered by least recently used first, then last processes position unchanged. |
| assertThat(processList).containsExactly(lastUsed60MinutesAgo, lastUsed42MinutesAgo, |
| lastUsed40MinutesAgo, lastUsed17MinutesAgo, lastUsed15MinutesAgo, |
| lastUsed30MinutesAgo).inOrder(); |
| } |
| |
| @Test |
| public void reRankLruCachedApps_rssImpactsOrdering() throws InterruptedException { |
| setConfig(/* numberToReRank= */ 6, |
| /* preserveTopNApps= */ 0, |
| /* useFrequentRss= */ true, |
| /* rssUpdateRateMs= */ 0, |
| /* usesWeight= */ 0.0f, |
| /* pssWeight= */ 1.0f, |
| /* lruWeight= */ 0.0f); |
| |
| ProcessList list = new ProcessList(); |
| ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); |
| ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| processList.add(rss10k); |
| ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| processList.add(rss20k); |
| ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000); |
| processList.add(rss1k); |
| ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| processList.add(rss100k); |
| ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); |
| processList.add(rss2k); |
| ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20); |
| processList.add(rss15k); |
| // Only re-ranking 6 entries so this should stay in most recent position. |
| ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20); |
| processList.add(rss16k); |
| list.setLruProcessServiceStartLSP(processList.size()); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| |
| // First 6 ordered by largest pss, then last processes position unchanged. |
| assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, |
| rss16k).inOrder(); |
| } |
| |
| @Test |
| public void reRankLruCachedApps_rssImpactsOrdering_cachedRssValues() |
| throws InterruptedException { |
| setConfig(/* numberToReRank= */ 6, |
| /* preserveTopNApps= */ 0, |
| /* useFrequentRss= */ true, |
| /* rssUpdateRateMs= */ 10000000, |
| /* usesWeight= */ 0.0f, |
| /* pssWeight= */ 1.0f, |
| /* lruWeight= */ 0.0f); |
| |
| ProcessList list = new ProcessList(); |
| ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); |
| ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| processList.add(rss10k); |
| ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| processList.add(rss20k); |
| ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000); |
| processList.add(rss1k); |
| ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| processList.add(rss100k); |
| ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); |
| processList.add(rss2k); |
| ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20); |
| processList.add(rss15k); |
| // Only re-ranking 6 entries so this should stay in most recent position. |
| ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20); |
| processList.add(rss16k); |
| list.setLruProcessServiceStartLSP(processList.size()); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| // First 6 ordered by largest pss, then last processes position unchanged. |
| assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, |
| rss16k).inOrder(); |
| |
| // Clear mPidToRss so that Process.getRss calls fail. |
| mPidToRss.clear(); |
| // Mix up the process list to ensure that CacheOomRanker actually re-ranks. |
| Collections.swap(processList, 0, 1); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| // Re ranking is the same. |
| assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, |
| rss16k).inOrder(); |
| } |
| |
| @Test |
| public void reRankLruCachedApps_rssImpactsOrdering_profileRss() |
| throws InterruptedException { |
| setConfig(/* numberToReRank= */ 6, |
| /* preserveTopNApps= */ 0, |
| /* useFrequentRss= */ false, |
| /* rssUpdateRateMs= */ 10000000, |
| /* usesWeight= */ 0.0f, |
| /* pssWeight= */ 1.0f, |
| /* lruWeight= */ 0.0f); |
| |
| ProcessList list = new ProcessList(); |
| ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); |
| ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 0L, 1000); |
| rss10k.mProfile.setLastRss(10 * 1024L); |
| processList.add(rss10k); |
| ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 0L, 2000); |
| rss20k.mProfile.setLastRss(20 * 1024L); |
| processList.add(rss20k); |
| ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10000); |
| rss1k.mProfile.setLastRss(1024L); |
| processList.add(rss1k); |
| ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10); |
| rss100k.mProfile.setLastRss(100 * 1024L); |
| processList.add(rss100k); |
| ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 0L, 20); |
| rss2k.mProfile.setLastRss(2 * 1024L); |
| processList.add(rss2k); |
| ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20); |
| rss15k.mProfile.setLastRss(15 * 1024L); |
| processList.add(rss15k); |
| // Only re-ranking 6 entries so this should stay in most recent position. |
| ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20); |
| rss16k.mProfile.setLastRss(16 * 1024L); |
| processList.add(rss16k); |
| list.setLruProcessServiceStartLSP(processList.size()); |
| |
| // This should not be used, as RSS values are taken from mProfile. |
| mPidToRss.clear(); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| // First 6 ordered by largest pss, then last processes position unchanged. |
| assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, |
| rss16k).inOrder(); |
| |
| // Clear mPidToRss so that Process.getRss calls fail. |
| mPidToRss.clear(); |
| // Mix up the process list to ensure that CacheOomRanker actually re-ranks. |
| Collections.swap(processList, 0, 1); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| // Re ranking is the same. |
| assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, |
| rss16k).inOrder(); |
| } |
| |
| |
| @Test |
| public void reRankLruCachedApps_usesImpactsOrdering() throws InterruptedException { |
| setConfig(/* numberToReRank= */ 4, |
| /* preserveTopNApps= */ 0, |
| /* useFrequentRss= */ true, |
| /* rssUpdateRateMs= */ 0, |
| /* usesWeight= */ 1.0f, |
| /* pssWeight= */ 0.0f, |
| /* lruWeight= */ 0.0f); |
| |
| ProcessList list = new ProcessList(); |
| ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); |
| ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| processList.add(used1000); |
| ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| processList.add(used2000); |
| ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| processList.add(used10); |
| ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); |
| processList.add(used20); |
| // Only re-ranking 6 entries so last two should stay in most recent position. |
| ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); |
| processList.add(used500); |
| ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); |
| processList.add(used200); |
| list.setLruProcessServiceStartLSP(processList.size()); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| |
| // First 4 ordered by uses, then last processes position unchanged. |
| assertThat(processList).containsExactly(used10, used20, used1000, used2000, used500, |
| used200).inOrder(); |
| } |
| |
| @Test |
| public void reRankLruCachedApps_fewProcesses() throws InterruptedException { |
| setConfig(/* numberToReRank= */ 4, |
| /* preserveTopNApps= */ 0, |
| /* useFrequentRss= */ true, |
| /* rssUpdateRateMs= */ 0, |
| /* usesWeight= */ 1.0f, |
| /* pssWeight= */ 0.0f, |
| /* lruWeight= */ 0.0f); |
| |
| ProcessList list = new ProcessList(); |
| ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); |
| ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| processList.add(used1000); |
| ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| processList.add(used2000); |
| ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| processList.add(used10); |
| ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); |
| processList.add(foregroundAdj); |
| ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); |
| processList.add(serviceAdj); |
| ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); |
| processList.add(systemAdj); |
| list.setLruProcessServiceStartLSP(processList.size()); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| |
| // 6 processes, only 3 in eligible for cache, so only those are re-ranked. |
| assertThat(processList).containsExactly(used10, used1000, used2000, |
| foregroundAdj, serviceAdj, systemAdj).inOrder(); |
| } |
| |
| @Test |
| public void reRankLruCachedApps_fewNonServiceProcesses() throws InterruptedException { |
| setConfig(/* numberToReRank= */ 4, |
| /* preserveTopNApps= */ 0, |
| /* useFrequentRss= */ true, |
| /* rssUpdateRateMs= */ 0, |
| /* usesWeight= */ 1.0f, |
| /* pssWeight= */ 0.0f, |
| /* lruWeight= */ 0.0f); |
| |
| ProcessList list = new ProcessList(); |
| ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); |
| ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| processList.add(used1000); |
| ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| processList.add(used2000); |
| ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| processList.add(used10); |
| ProcessRecord service1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); |
| processList.add(service1); |
| ProcessRecord service2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); |
| processList.add(service2); |
| ProcessRecord service3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); |
| processList.add(service3); |
| list.setLruProcessServiceStartLSP(3); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| |
| // Services unchanged, rest re-ranked. |
| assertThat(processList).containsExactly(used10, used1000, used2000, service1, service2, |
| service3).inOrder(); |
| } |
| |
| @Test |
| public void reRankLruCachedApps_manyProcessesThenFew() throws InterruptedException { |
| setConfig(/* numberToReRank= */ 6, |
| /* preserveTopNApps= */ 0, |
| /* useFrequentRss= */ true, |
| /* rssUpdateRateMs= */ 0, |
| /* usesWeight= */ 1.0f, |
| /* pssWeight= */ 0.0f, |
| /* lruWeight= */ 0.0f); |
| |
| ProcessList set1List = new ProcessList(); |
| ArrayList<ProcessRecord> set1ProcessList = set1List.getLruProcessesLSP(); |
| ProcessRecord set1Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| set1ProcessList.add(set1Used1000); |
| ProcessRecord set1Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| set1ProcessList.add(set1Used2000); |
| ProcessRecord set1Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| set1ProcessList.add(set1Used10); |
| ProcessRecord set1Uses20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); |
| set1ProcessList.add(set1Uses20); |
| ProcessRecord set1Uses500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); |
| set1ProcessList.add(set1Uses500); |
| ProcessRecord set1Uses200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); |
| set1ProcessList.add(set1Uses200); |
| set1List.setLruProcessServiceStartLSP(set1ProcessList.size()); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(set1ProcessList, |
| set1List.getLruProcessServiceStartLOSP()); |
| assertThat(set1ProcessList).containsExactly(set1Used10, set1Uses20, set1Uses200, |
| set1Uses500, set1Used1000, set1Used2000).inOrder(); |
| |
| ProcessList set2List = new ProcessList(); |
| ArrayList<ProcessRecord> set2ProcessList = set2List.getLruProcessesLSP(); |
| ProcessRecord set2Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| set2ProcessList.add(set2Used1000); |
| ProcessRecord set2Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| set2ProcessList.add(set2Used2000); |
| ProcessRecord set2Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| set2ProcessList.add(set2Used10); |
| ProcessRecord set2ForegroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); |
| set2ProcessList.add(set2ForegroundAdj); |
| ProcessRecord set2ServiceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); |
| set2ProcessList.add(set2ServiceAdj); |
| ProcessRecord set2SystemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); |
| set2ProcessList.add(set2SystemAdj); |
| set2List.setLruProcessServiceStartLSP(set2ProcessList.size()); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(set2ProcessList, |
| set2List.getLruProcessServiceStartLOSP()); |
| assertThat(set2ProcessList).containsExactly(set2Used10, set2Used1000, set2Used2000, |
| set2ForegroundAdj, set2ServiceAdj, set2SystemAdj).inOrder(); |
| } |
| |
| @Test |
| public void reRankLruCachedApps_preservesTopNApps() throws InterruptedException { |
| setConfig(/* numberToReRank= */ 6, |
| /* preserveTopNApps= */ 3, |
| /* useFrequentRss= */ true, |
| /* rssUpdateRateMs= */ 0, |
| /* usesWeight= */ 1.0f, |
| /* pssWeight= */ 0.0f, |
| /* lruWeight= */ 0.0f); |
| |
| ProcessList list = new ProcessList(); |
| ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); |
| ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| processList.add(used1000); |
| ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| processList.add(used2000); |
| ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| processList.add(used10); |
| // Preserving the top 3 processes, so these should not be re-ranked. |
| ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); |
| processList.add(used20); |
| ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); |
| processList.add(used500); |
| ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); |
| processList.add(used200); |
| list.setLruProcessServiceStartLSP(processList.size()); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| |
| // First 3 ordered by uses, then last processes position unchanged. |
| assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500, |
| used200).inOrder(); |
| } |
| |
| @Test |
| public void reRankLruCachedApps_preservesTopNApps_allAppsUnchanged() |
| throws InterruptedException { |
| setConfig(/* numberToReRank= */ 6, |
| /* preserveTopNApps= */ 100, |
| /* useFrequentRss= */ true, |
| /* rssUpdateRateMs= */ 0, |
| /* usesWeight= */ 1.0f, |
| /* pssWeight= */ 0.0f, |
| /* lruWeight= */ 0.0f); |
| |
| ProcessList list = new ProcessList(); |
| ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); |
| ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| processList.add(used1000); |
| ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| processList.add(used2000); |
| ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| processList.add(used10); |
| ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); |
| processList.add(used20); |
| ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); |
| processList.add(used500); |
| ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); |
| processList.add(used200); |
| list.setLruProcessServiceStartLSP(processList.size()); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| |
| // Nothing reordered, as we preserve the top 100 apps. |
| assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500, |
| used200).inOrder(); |
| } |
| |
| @Test |
| public void reRankLruCachedApps_preservesTopNApps_negativeReplacedWithDefault() |
| throws InterruptedException { |
| setConfig(/* numberToReRank= */ 6, |
| /* preserveTopNApps= */ -100, |
| /* useFrequentRss= */ true, |
| /* rssUpdateRateMs= */ 0, |
| /* usesWeight= */ 1.0f, |
| /* pssWeight= */ 0.0f, |
| /* lruWeight= */ 0.0f); |
| |
| ProcessList list = new ProcessList(); |
| ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); |
| ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000); |
| processList.add(used1000); |
| ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000); |
| processList.add(used2000); |
| ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10); |
| processList.add(used10); |
| // Negative preserveTopNApps interpreted as the default (3), so the last three are unranked. |
| ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20); |
| processList.add(used20); |
| ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500); |
| processList.add(used500); |
| ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, |
| NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200); |
| processList.add(used200); |
| list.setLruProcessServiceStartLSP(processList.size()); |
| |
| mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); |
| |
| // First 3 apps re-ranked, as preserveTopNApps is interpreted as 3. |
| assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500, |
| used200).inOrder(); |
| } |
| |
| private void setConfig(int numberToReRank, int preserveTopNApps, boolean useFrequentRss, |
| long rssUpdateRateMs, float usesWeight, float pssWeight, float lruWeight) |
| throws InterruptedException { |
| mExecutor.init(4); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK, |
| Integer.toString(numberToReRank), |
| false); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, |
| Integer.toString(preserveTopNApps), |
| false); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_USE_FREQUENT_RSS, |
| Boolean.toString(useFrequentRss), |
| false); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS, |
| Long.toString(rssUpdateRateMs), |
| false); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT, |
| Float.toString(lruWeight), |
| false); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_RSS_WEIGHT, |
| Float.toString(pssWeight), |
| false); |
| DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT, |
| Float.toString(usesWeight), |
| false); |
| mExecutor.waitForLatch(); |
| assertThat(mCacheOomRanker.getNumberToReRank()).isEqualTo(numberToReRank); |
| assertThat(mCacheOomRanker.mUseFrequentRss).isEqualTo(useFrequentRss); |
| assertThat(mCacheOomRanker.mRssUpdateRateMs).isEqualTo(rssUpdateRateMs); |
| assertThat(mCacheOomRanker.mRssWeight).isEqualTo(pssWeight); |
| assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(usesWeight); |
| assertThat(mCacheOomRanker.mLruWeight).isEqualTo(lruWeight); |
| } |
| |
| private ProcessRecord nextProcessRecord(int setAdj, long lastActivityTime, long lastRss, |
| int wentToForegroundCount) { |
| ApplicationInfo ai = new ApplicationInfo(); |
| ai.packageName = "a.package.name" + mNextPackageName++; |
| ProcessRecord app = new ProcessRecord(mAms, ai, ai.packageName + ":process", mNextUid++); |
| app.setPid(mNextPid++); |
| app.info.uid = mNextPackageUid++; |
| // Exact value does not mater, it can be any state for which compaction is allowed. |
| app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE); |
| app.mState.setCurAdj(setAdj); |
| app.setLastActivityTime(lastActivityTime); |
| mPidToRss.put(app.getPid(), lastRss); |
| app.mState.setCached(false); |
| for (int i = 0; i < wentToForegroundCount; ++i) { |
| app.mState.setSetProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); |
| app.mState.setSetProcState(ActivityManager.PROCESS_STATE_CACHED_RECENT); |
| } |
| // Sets the thread returned by ProcessRecord#getThread, which we use to check whether the |
| // app is currently launching. |
| ProcessStatsService processStatsService = new ProcessStatsService( |
| mock(ActivityManagerService.class), new File(Environment.getDataSystemCeDirectory(), |
| "procstats")); |
| app.makeActive(mock(IApplicationThread.class), processStatsService); |
| return app; |
| } |
| |
| private class TestExecutor implements Executor { |
| private CountDownLatch mLatch; |
| |
| private void init(int count) { |
| mLatch = new CountDownLatch(count); |
| } |
| |
| private void init() { |
| init(1); |
| } |
| |
| private void waitForLatch() throws InterruptedException { |
| mLatch.await(5, TimeUnit.SECONDS); |
| } |
| |
| @Override |
| public void execute(Runnable command) { |
| command.run(); |
| mLatch.countDown(); |
| } |
| } |
| |
| private class TestInjector extends ActivityManagerService.Injector { |
| private TestInjector(Context context) { |
| super(context); |
| } |
| |
| @Override |
| public AppOpsService getAppOpsService(File file, Handler handler) { |
| return mAppOpsService; |
| } |
| |
| @Override |
| public Handler getUiHandler(ActivityManagerService service) { |
| return mHandler; |
| } |
| } |
| } |