• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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