1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.am; 18 19 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.mockito.Mockito.doReturn; 24 import static org.mockito.Mockito.spy; 25 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.PackageManagerInternal; 30 import android.os.Handler; 31 import android.os.HandlerThread; 32 import android.os.Process; 33 import android.provider.DeviceConfig; 34 35 import androidx.test.platform.app.InstrumentationRegistry; 36 37 import com.android.server.LocalServices; 38 import com.android.server.ServiceThread; 39 import com.android.server.appop.AppOpsService; 40 import com.android.server.testables.TestableDeviceConfig; 41 import com.android.server.wm.ActivityTaskManagerService; 42 43 import org.junit.Before; 44 import org.junit.Rule; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 import org.mockito.Mock; 48 import org.mockito.junit.MockitoJUnitRunner; 49 50 import java.io.File; 51 import java.time.Duration; 52 import java.util.ArrayList; 53 import java.util.concurrent.CountDownLatch; 54 import java.util.concurrent.Executor; 55 import java.util.concurrent.TimeUnit; 56 57 /** 58 * Tests for {@link CachedAppOptimizer}. 59 * 60 * Build/Install/Run: 61 * atest FrameworksMockingServicesTests:CacheOomRankerTest 62 */ 63 @RunWith(MockitoJUnitRunner.class) 64 public class CacheOomRankerTest { 65 66 @Mock 67 private AppOpsService mAppOpsService; 68 private Handler mHandler; 69 private ActivityManagerService mAms; 70 71 @Mock 72 private PackageManagerInternal mPackageManagerInt; 73 74 @Rule 75 public final TestableDeviceConfig.TestableDeviceConfigRule 76 mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule(); 77 @Rule 78 public final ApplicationExitInfoTest.ServiceThreadRule 79 mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); 80 81 private int mNextPid = 10000; 82 private int mNextUid = 30000; 83 private int mNextPackageUid = 40000; 84 private int mNextPackageName = 1; 85 86 private TestExecutor mExecutor = new TestExecutor(); 87 private CacheOomRanker mCacheOomRanker; 88 89 @Before setUp()90 public void setUp() { 91 HandlerThread handlerThread = new HandlerThread(""); 92 handlerThread.start(); 93 mHandler = new Handler(handlerThread.getLooper()); 94 /* allowIo */ 95 ServiceThread thread = new ServiceThread("TestServiceThread", 96 Process.THREAD_PRIORITY_DEFAULT, 97 true /* allowIo */); 98 thread.start(); 99 Context context = InstrumentationRegistry.getInstrumentation().getContext(); 100 mAms = new ActivityManagerService( 101 new TestInjector(context), mServiceThreadRule.getThread()); 102 mAms.mActivityTaskManager = new ActivityTaskManagerService(context); 103 mAms.mActivityTaskManager.initialize(null, null, context.getMainLooper()); 104 mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal()); 105 mAms.mPackageManagerInt = mPackageManagerInt; 106 doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); 107 LocalServices.removeServiceForTest(PackageManagerInternal.class); 108 LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); 109 110 mCacheOomRanker = new CacheOomRanker(mAms); 111 mCacheOomRanker.init(mExecutor); 112 } 113 114 @Test init_listensForConfigChanges()115 public void init_listensForConfigChanges() throws InterruptedException { 116 mExecutor.init(); 117 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 118 CacheOomRanker.KEY_USE_OOM_RE_RANKING, 119 Boolean.TRUE.toString(), true); 120 mExecutor.waitForLatch(); 121 assertThat(mCacheOomRanker.useOomReranking()).isTrue(); 122 mExecutor.init(); 123 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 124 CacheOomRanker.KEY_USE_OOM_RE_RANKING, Boolean.FALSE.toString(), false); 125 mExecutor.waitForLatch(); 126 assertThat(mCacheOomRanker.useOomReranking()).isFalse(); 127 128 mExecutor.init(); 129 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 130 CacheOomRanker.KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK, 131 Integer.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK + 2), 132 false); 133 mExecutor.waitForLatch(); 134 assertThat(mCacheOomRanker.getNumberToReRank()) 135 .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK + 2); 136 137 mExecutor.init(); 138 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 139 CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT, 140 Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f), 141 false); 142 mExecutor.waitForLatch(); 143 assertThat(mCacheOomRanker.mLruWeight) 144 .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f); 145 146 mExecutor.init(); 147 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 148 CacheOomRanker.KEY_OOM_RE_RANKING_RSS_WEIGHT, 149 Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_RSS_WEIGHT - 0.1f), 150 false); 151 mExecutor.waitForLatch(); 152 assertThat(mCacheOomRanker.mRssWeight) 153 .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_RSS_WEIGHT - 0.1f); 154 155 mExecutor.init(); 156 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 157 CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT, 158 Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_USES_WEIGHT + 0.2f), 159 false); 160 mExecutor.waitForLatch(); 161 assertThat(mCacheOomRanker.mUsesWeight) 162 .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_USES_WEIGHT + 0.2f); 163 } 164 165 @Test reRankLruCachedApps_lruImpactsOrdering()166 public void reRankLruCachedApps_lruImpactsOrdering() throws InterruptedException { 167 setConfig(/* numberToReRank= */ 5, 168 /* usesWeight= */ 0.0f, 169 /* pssWeight= */ 0.0f, 170 /* lruWeight= */1.0f); 171 172 ProcessList list = new ProcessList(); 173 ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); 174 ProcessRecord lastUsed40MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 175 Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); 176 processList.add(lastUsed40MinutesAgo); 177 ProcessRecord lastUsed42MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 178 Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); 179 processList.add(lastUsed42MinutesAgo); 180 ProcessRecord lastUsed60MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 181 Duration.ofMinutes(60).toMillis(), 1024L, 10000); 182 processList.add(lastUsed60MinutesAgo); 183 ProcessRecord lastUsed15MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 184 Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); 185 processList.add(lastUsed15MinutesAgo); 186 ProcessRecord lastUsed17MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 187 Duration.ofMinutes(17).toMillis(), 1024L, 20); 188 processList.add(lastUsed17MinutesAgo); 189 // Only re-ranking 5 entries so this should stay in most recent position. 190 ProcessRecord lastUsed30MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 191 Duration.ofMinutes(30).toMillis(), 1024L, 20); 192 processList.add(lastUsed30MinutesAgo); 193 194 mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); 195 196 // First 5 ordered by least recently used first, then last processes position unchanged. 197 assertThat(processList).containsExactly(lastUsed60MinutesAgo, lastUsed42MinutesAgo, 198 lastUsed40MinutesAgo, lastUsed17MinutesAgo, lastUsed15MinutesAgo, 199 lastUsed30MinutesAgo); 200 } 201 202 @Test reRankLruCachedApps_rssImpactsOrdering()203 public void reRankLruCachedApps_rssImpactsOrdering() throws InterruptedException { 204 setConfig(/* numberToReRank= */ 6, 205 /* usesWeight= */ 0.0f, 206 /* pssWeight= */ 1.0f, 207 /* lruWeight= */ 0.0f); 208 209 ProcessList list = new ProcessList(); 210 ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); 211 ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 212 Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); 213 processList.add(rss10k); 214 ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 215 Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); 216 processList.add(rss20k); 217 ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 218 Duration.ofMinutes(60).toMillis(), 1024L, 10000); 219 processList.add(rss1k); 220 ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 221 Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); 222 processList.add(rss100k); 223 ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 224 Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20); 225 processList.add(rss2k); 226 ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 227 Duration.ofMinutes(30).toMillis(), 15 * 1024L, 20); 228 processList.add(rss15k); 229 // Only re-ranking 6 entries so this should stay in most recent position. 230 ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 231 Duration.ofMinutes(30).toMillis(), 16 * 1024L, 20); 232 processList.add(rss16k); 233 234 mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); 235 236 // First 6 ordered by largest pss, then last processes position unchanged. 237 assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k, 238 rss16k); 239 } 240 241 @Test reRankLruCachedApps_usesImpactsOrdering()242 public void reRankLruCachedApps_usesImpactsOrdering() throws InterruptedException { 243 setConfig(/* numberToReRank= */ 4, 244 /* usesWeight= */ 1.0f, 245 /* pssWeight= */ 0.0f, 246 /* lruWeight= */ 0.0f); 247 248 ProcessList list = new ProcessList(); 249 list.setLruProcessServiceStartLSP(1); 250 ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); 251 ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 252 Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); 253 processList.add(used1000); 254 ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 255 Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); 256 processList.add(used2000); 257 ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 258 Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); 259 processList.add(used10); 260 ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 261 Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20); 262 processList.add(used20); 263 // Only re-ranking 6 entries so last two should stay in most recent position. 264 ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 265 Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500); 266 processList.add(used500); 267 ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 268 Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200); 269 processList.add(used200); 270 271 mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); 272 273 // First 4 ordered by uses, then last processes position unchanged. 274 assertThat(processList).containsExactly(used10, used20, used1000, used2000, used500, 275 used200); 276 } 277 278 @Test reRankLruCachedApps_notEnoughProcesses()279 public void reRankLruCachedApps_notEnoughProcesses() throws InterruptedException { 280 setConfig(/* numberToReRank= */ 4, 281 /* usesWeight= */ 0.5f, 282 /* pssWeight= */ 0.2f, 283 /* lruWeight= */ 0.3f); 284 285 ProcessList list = new ProcessList(); 286 ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); 287 ProcessRecord unknownAdj1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 288 Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); 289 processList.add(unknownAdj1); 290 ProcessRecord unknownAdj2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 291 Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); 292 processList.add(unknownAdj2); 293 ProcessRecord unknownAdj3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 294 Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); 295 processList.add(unknownAdj3); 296 ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ, 297 Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20); 298 processList.add(foregroundAdj); 299 ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ, 300 Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500); 301 processList.add(serviceAdj); 302 ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ, 303 Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200); 304 processList.add(systemAdj); 305 306 // 6 Processes but only 3 in eligible for cache so no re-ranking. 307 mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); 308 309 // All positions unchanged. 310 assertThat(processList).containsExactly(unknownAdj1, unknownAdj2, unknownAdj3, 311 foregroundAdj, serviceAdj, systemAdj); 312 } 313 314 @Test reRankLruCachedApps_notEnoughNonServiceProcesses()315 public void reRankLruCachedApps_notEnoughNonServiceProcesses() throws InterruptedException { 316 setConfig(/* numberToReRank= */ 4, 317 /* usesWeight= */ 1.0f, 318 /* pssWeight= */ 0.0f, 319 /* lruWeight= */ 0.0f); 320 321 ProcessList list = new ProcessList(); 322 list.setLruProcessServiceStartLSP(4); 323 ArrayList<ProcessRecord> processList = list.getLruProcessesLSP(); 324 ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 325 Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000); 326 processList.add(used1000); 327 ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 328 Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000); 329 processList.add(used2000); 330 ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 331 Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10); 332 processList.add(used10); 333 ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 334 Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20); 335 processList.add(used20); 336 ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 337 Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500); 338 processList.add(used500); 339 ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ, 340 Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200); 341 processList.add(used200); 342 343 mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP()); 344 345 // All positions unchanged. 346 assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500, 347 used200); 348 } 349 setConfig(int numberToReRank, float useWeight, float pssWeight, float lruWeight)350 private void setConfig(int numberToReRank, float useWeight, float pssWeight, float lruWeight) 351 throws InterruptedException { 352 mExecutor.init(4); 353 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 354 CacheOomRanker.KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK, 355 Integer.toString(numberToReRank), 356 false); 357 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 358 CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT, 359 Float.toString(lruWeight), 360 false); 361 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 362 CacheOomRanker.KEY_OOM_RE_RANKING_RSS_WEIGHT, 363 Float.toString(pssWeight), 364 false); 365 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 366 CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT, 367 Float.toString(useWeight), 368 false); 369 mExecutor.waitForLatch(); 370 assertThat(mCacheOomRanker.getNumberToReRank()).isEqualTo(numberToReRank); 371 assertThat(mCacheOomRanker.mRssWeight).isEqualTo(pssWeight); 372 assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(useWeight); 373 assertThat(mCacheOomRanker.mLruWeight).isEqualTo(lruWeight); 374 } 375 nextProcessRecord(int setAdj, long lastActivityTime, long lastRss, int returnedToCacheCount)376 private ProcessRecord nextProcessRecord(int setAdj, long lastActivityTime, long lastRss, 377 int returnedToCacheCount) { 378 ApplicationInfo ai = new ApplicationInfo(); 379 ai.packageName = "a.package.name" + mNextPackageName++; 380 ProcessRecord app = new ProcessRecord(mAms, ai, ai.packageName + ":process", mNextUid++); 381 app.setPid(mNextPid++); 382 app.info.uid = mNextPackageUid++; 383 // Exact value does not mater, it can be any state for which compaction is allowed. 384 app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE); 385 app.mState.setSetAdj(setAdj); 386 app.setLastActivityTime(lastActivityTime); 387 app.mProfile.setLastRss(lastRss); 388 app.mState.setCached(false); 389 for (int i = 0; i < returnedToCacheCount; ++i) { 390 app.mState.setCached(false); 391 app.mState.setCached(true); 392 } 393 return app; 394 } 395 396 private class TestExecutor implements Executor { 397 private CountDownLatch mLatch; 398 init(int count)399 private void init(int count) { 400 mLatch = new CountDownLatch(count); 401 } 402 init()403 private void init() { 404 init(1); 405 } 406 waitForLatch()407 private void waitForLatch() throws InterruptedException { 408 mLatch.await(5, TimeUnit.SECONDS); 409 } 410 411 @Override execute(Runnable command)412 public void execute(Runnable command) { 413 command.run(); 414 mLatch.countDown(); 415 } 416 } 417 418 private class TestInjector extends ActivityManagerService.Injector { TestInjector(Context context)419 private TestInjector(Context context) { 420 super(context); 421 } 422 423 @Override getAppOpsService(File file, Handler handler)424 public AppOpsService getAppOpsService(File file, Handler handler) { 425 return mAppOpsService; 426 } 427 428 @Override getUiHandler(ActivityManagerService service)429 public Handler getUiHandler(ActivityManagerService service) { 430 return mHandler; 431 } 432 } 433 } 434