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.mock;
25 import static org.mockito.Mockito.spy;
26 
27 import android.app.ActivityManager;
28 import android.app.IApplicationThread;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.pm.ApplicationInfo;
32 import android.content.pm.PackageManagerInternal;
33 import android.os.Environment;
34 import android.os.Handler;
35 import android.os.HandlerThread;
36 import android.os.Process;
37 import android.provider.DeviceConfig;
38 
39 import androidx.test.platform.app.InstrumentationRegistry;
40 
41 import com.android.modules.utils.testing.TestableDeviceConfig;
42 import com.android.server.LocalServices;
43 import com.android.server.ServiceThread;
44 import com.android.server.appop.AppOpsService;
45 import com.android.server.wm.ActivityTaskManagerService;
46 
47 import org.junit.Before;
48 import org.junit.Rule;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.mockito.Mock;
52 import org.mockito.junit.MockitoJUnitRunner;
53 
54 import java.io.File;
55 import java.time.Instant;
56 import java.time.LocalDate;
57 import java.time.ZoneOffset;
58 import java.time.temporal.ChronoUnit;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.HashMap;
62 import java.util.Map;
63 import java.util.concurrent.CountDownLatch;
64 import java.util.concurrent.Executor;
65 import java.util.concurrent.TimeUnit;
66 
67 /**
68  * Tests for {@link CachedAppOptimizer}.
69  *
70  * Build/Install/Run:
71  * atest FrameworksMockingServicesTests:CacheOomRankerTest
72  */
73 @SuppressWarnings("GuardedBy") // No tests are concurrent, so no need to test locking.
74 @RunWith(MockitoJUnitRunner.class)
75 public class CacheOomRankerTest {
76     private static final Instant NOW = LocalDate.of(2021, 1, 1).atStartOfDay(
77             ZoneOffset.UTC).toInstant();
78 
79     @Mock
80     private AppOpsService mAppOpsService;
81     private Handler mHandler;
82     private ActivityManagerService mAms;
83 
84     @Mock
85     private PackageManagerInternal mPackageManagerInt;
86 
87     @Rule
88     public final TestableDeviceConfig.TestableDeviceConfigRule
89             mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
90     @Rule
91     public final ApplicationExitInfoTest.ServiceThreadRule
92             mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
93 
94     private int mNextPid = 10000;
95     private int mNextUid = 30000;
96     private int mNextPackageUid = 40000;
97     private int mNextPackageName = 1;
98     private Map<Integer, Long> mPidToRss;
99 
100     private TestExecutor mExecutor = new TestExecutor();
101     private CacheOomRanker mCacheOomRanker;
102 
103     @Before
setUp()104     public void setUp() {
105         HandlerThread handlerThread = new HandlerThread("");
106         handlerThread.start();
107         mHandler = new Handler(handlerThread.getLooper());
108         /* allowIo */
109         ServiceThread thread = new ServiceThread("TestServiceThread",
110                 Process.THREAD_PRIORITY_DEFAULT,
111                 true /* allowIo */);
112         thread.start();
113         Context context = InstrumentationRegistry.getInstrumentation().getContext();
114         mAms = new ActivityManagerService(
115                 new TestInjector(context), mServiceThreadRule.getThread());
116         mAms.mActivityTaskManager = new ActivityTaskManagerService(context);
117         mAms.mActivityTaskManager.initialize(null, null, context.getMainLooper());
118         mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
119         mAms.mPackageManagerInt = mPackageManagerInt;
120         doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
121         LocalServices.removeServiceForTest(PackageManagerInternal.class);
122         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
123 
124         mPidToRss = new HashMap<>();
125         mCacheOomRanker = new CacheOomRanker(
126                 mAms,
127                 pid -> {
128                     Long rss = mPidToRss.get(pid);
129                     assertThat(rss).isNotNull();
130                     return new long[]{rss};
131                 }
132         );
133         mCacheOomRanker.init(mExecutor);
134     }
135 
136     @Test
init_listensForConfigChanges()137     public void init_listensForConfigChanges() throws InterruptedException {
138         mExecutor.init();
139         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
140                 CacheOomRanker.KEY_USE_OOM_RE_RANKING,
141                 Boolean.TRUE.toString(), true);
142         mExecutor.waitForLatch();
143         assertThat(mCacheOomRanker.useOomReranking()).isTrue();
144         mExecutor.init();
145         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
146                 CacheOomRanker.KEY_USE_OOM_RE_RANKING, Boolean.FALSE.toString(), false);
147         mExecutor.waitForLatch();
148         assertThat(mCacheOomRanker.useOomReranking()).isFalse();
149 
150         mExecutor.init();
151         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
152                 CacheOomRanker.KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK,
153                 Integer.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK + 2),
154                 false);
155         mExecutor.waitForLatch();
156         assertThat(mCacheOomRanker.getNumberToReRank())
157                 .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK + 2);
158 
159         mExecutor.init();
160         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
161                 CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS,
162                 Integer.toString(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1),
163                 false);
164         mExecutor.waitForLatch();
165         assertThat(mCacheOomRanker.mPreserveTopNApps)
166                 .isEqualTo(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1);
167 
168         mExecutor.init();
169         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
170                 CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT,
171                 Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f),
172                 false);
173         mExecutor.waitForLatch();
174         assertThat(mCacheOomRanker.mLruWeight)
175                 .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f);
176 
177         mExecutor.init();
178         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
179                 CacheOomRanker.KEY_OOM_RE_RANKING_RSS_WEIGHT,
180                 Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_RSS_WEIGHT - 0.1f),
181                 false);
182         mExecutor.waitForLatch();
183         assertThat(mCacheOomRanker.mRssWeight)
184                 .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_RSS_WEIGHT - 0.1f);
185 
186         mExecutor.init();
187         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
188                 CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT,
189                 Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_USES_WEIGHT + 0.2f),
190                 false);
191         mExecutor.waitForLatch();
192         assertThat(mCacheOomRanker.mUsesWeight)
193                 .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_USES_WEIGHT + 0.2f);
194     }
195 
196     @Test
reRankLruCachedApps_lruImpactsOrdering()197     public void reRankLruCachedApps_lruImpactsOrdering() throws InterruptedException {
198         setConfig(/* numberToReRank= */ 5,
199                 /* preserveTopNApps= */ 0,
200                 /* useFrequentRss= */ true,
201                 /* rssUpdateRateMs= */ 0,
202                 /* usesWeight= */ 0.0f,
203                 /* pssWeight= */ 0.0f,
204                 /* lruWeight= */1.0f);
205 
206         ProcessList list = new ProcessList();
207         ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
208         ProcessRecord lastUsed40MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
209                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
210         processList.add(lastUsed40MinutesAgo);
211         ProcessRecord lastUsed42MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
212                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
213         processList.add(lastUsed42MinutesAgo);
214         ProcessRecord lastUsed60MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
215                 NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
216         processList.add(lastUsed60MinutesAgo);
217         ProcessRecord lastUsed15MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
218                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
219         processList.add(lastUsed15MinutesAgo);
220         ProcessRecord lastUsed17MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
221                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20);
222         processList.add(lastUsed17MinutesAgo);
223         // Only re-ranking 5 entries so this should stay in most recent position.
224         ProcessRecord lastUsed30MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
225                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20);
226         processList.add(lastUsed30MinutesAgo);
227         list.setLruProcessServiceStartLSP(processList.size());
228 
229         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
230 
231         // First 5 ordered by least recently used first, then last processes position unchanged.
232         assertThat(processList).containsExactly(lastUsed60MinutesAgo, lastUsed42MinutesAgo,
233                 lastUsed40MinutesAgo, lastUsed17MinutesAgo, lastUsed15MinutesAgo,
234                 lastUsed30MinutesAgo).inOrder();
235     }
236 
237     @Test
reRankLruCachedApps_rssImpactsOrdering()238     public void reRankLruCachedApps_rssImpactsOrdering() throws InterruptedException {
239         setConfig(/* numberToReRank= */ 6,
240                 /* preserveTopNApps= */ 0,
241                 /* useFrequentRss= */ true,
242                 /* rssUpdateRateMs= */ 0,
243                 /* usesWeight= */ 0.0f,
244                 /* pssWeight= */ 1.0f,
245                 /* lruWeight= */ 0.0f);
246 
247         ProcessList list = new ProcessList();
248         ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
249         ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
250                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
251         processList.add(rss10k);
252         ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
253                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
254         processList.add(rss20k);
255         ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
256                 NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
257         processList.add(rss1k);
258         ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
259                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
260         processList.add(rss100k);
261         ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
262                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
263         processList.add(rss2k);
264         ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
265                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
266         processList.add(rss15k);
267         // Only re-ranking 6 entries so this should stay in most recent position.
268         ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
269                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
270         processList.add(rss16k);
271         list.setLruProcessServiceStartLSP(processList.size());
272 
273         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
274 
275         // First 6 ordered by largest pss, then last processes position unchanged.
276         assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
277                 rss16k).inOrder();
278     }
279 
280     @Test
reRankLruCachedApps_rssImpactsOrdering_cachedRssValues()281     public void reRankLruCachedApps_rssImpactsOrdering_cachedRssValues()
282             throws InterruptedException {
283         setConfig(/* numberToReRank= */ 6,
284                 /* preserveTopNApps= */ 0,
285                 /* useFrequentRss= */ true,
286                 /* rssUpdateRateMs= */ 10000000,
287                 /* usesWeight= */ 0.0f,
288                 /* pssWeight= */ 1.0f,
289                 /* lruWeight= */ 0.0f);
290 
291         ProcessList list = new ProcessList();
292         ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
293         ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
294                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
295         processList.add(rss10k);
296         ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
297                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
298         processList.add(rss20k);
299         ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
300                 NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
301         processList.add(rss1k);
302         ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
303                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
304         processList.add(rss100k);
305         ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
306                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
307         processList.add(rss2k);
308         ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
309                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
310         processList.add(rss15k);
311         // Only re-ranking 6 entries so this should stay in most recent position.
312         ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
313                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
314         processList.add(rss16k);
315         list.setLruProcessServiceStartLSP(processList.size());
316 
317         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
318         // First 6 ordered by largest pss, then last processes position unchanged.
319         assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
320                 rss16k).inOrder();
321 
322         // Clear mPidToRss so that Process.getRss calls fail.
323         mPidToRss.clear();
324         // Mix up the process list to ensure that CacheOomRanker actually re-ranks.
325         Collections.swap(processList, 0, 1);
326 
327         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
328         // Re ranking is the same.
329         assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
330                 rss16k).inOrder();
331     }
332 
333     @Test
reRankLruCachedApps_rssImpactsOrdering_profileRss()334     public void reRankLruCachedApps_rssImpactsOrdering_profileRss()
335             throws InterruptedException {
336         setConfig(/* numberToReRank= */ 6,
337                 /* preserveTopNApps= */ 0,
338                 /* useFrequentRss= */ false,
339                 /* rssUpdateRateMs= */ 10000000,
340                 /* usesWeight= */ 0.0f,
341                 /* pssWeight= */ 1.0f,
342                 /* lruWeight= */ 0.0f);
343 
344         ProcessList list = new ProcessList();
345         ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
346         ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
347                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 0L, 1000);
348         rss10k.mProfile.setLastRss(10 * 1024L);
349         processList.add(rss10k);
350         ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
351                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 0L, 2000);
352         rss20k.mProfile.setLastRss(20 * 1024L);
353         processList.add(rss20k);
354         ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
355                 NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10000);
356         rss1k.mProfile.setLastRss(1024L);
357         processList.add(rss1k);
358         ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
359                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10);
360         rss100k.mProfile.setLastRss(100 * 1024L);
361         processList.add(rss100k);
362         ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
363                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 0L, 20);
364         rss2k.mProfile.setLastRss(2 * 1024L);
365         processList.add(rss2k);
366         ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
367                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
368         rss15k.mProfile.setLastRss(15 * 1024L);
369         processList.add(rss15k);
370         // Only re-ranking 6 entries so this should stay in most recent position.
371         ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
372                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
373         rss16k.mProfile.setLastRss(16 * 1024L);
374         processList.add(rss16k);
375         list.setLruProcessServiceStartLSP(processList.size());
376 
377         // This should not be used, as RSS values are taken from mProfile.
378         mPidToRss.clear();
379 
380         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
381         // First 6 ordered by largest pss, then last processes position unchanged.
382         assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
383                 rss16k).inOrder();
384 
385         // Clear mPidToRss so that Process.getRss calls fail.
386         mPidToRss.clear();
387         // Mix up the process list to ensure that CacheOomRanker actually re-ranks.
388         Collections.swap(processList, 0, 1);
389 
390         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
391         // Re ranking is the same.
392         assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
393                 rss16k).inOrder();
394     }
395 
396 
397     @Test
reRankLruCachedApps_usesImpactsOrdering()398     public void reRankLruCachedApps_usesImpactsOrdering() throws InterruptedException {
399         setConfig(/* numberToReRank= */ 4,
400                 /* preserveTopNApps= */ 0,
401                 /* useFrequentRss= */ true,
402                 /* rssUpdateRateMs= */ 0,
403                 /* usesWeight= */ 1.0f,
404                 /* pssWeight= */ 0.0f,
405                 /* lruWeight= */ 0.0f);
406 
407         ProcessList list = new ProcessList();
408         ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
409         ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
410                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
411         processList.add(used1000);
412         ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
413                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
414         processList.add(used2000);
415         ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
416                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
417         processList.add(used10);
418         ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
419                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
420         processList.add(used20);
421         // Only re-ranking 6 entries so last two should stay in most recent position.
422         ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
423                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
424         processList.add(used500);
425         ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
426                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
427         processList.add(used200);
428         list.setLruProcessServiceStartLSP(processList.size());
429 
430         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
431 
432         // First 4 ordered by uses, then last processes position unchanged.
433         assertThat(processList).containsExactly(used10, used20, used1000, used2000, used500,
434                 used200).inOrder();
435     }
436 
437     @Test
reRankLruCachedApps_fewProcesses()438     public void reRankLruCachedApps_fewProcesses() throws InterruptedException {
439         setConfig(/* numberToReRank= */ 4,
440                 /* preserveTopNApps= */ 0,
441                 /* useFrequentRss= */ true,
442                 /* rssUpdateRateMs= */ 0,
443                 /* usesWeight= */ 1.0f,
444                 /* pssWeight= */ 0.0f,
445                 /* lruWeight= */ 0.0f);
446 
447         ProcessList list = new ProcessList();
448         ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
449         ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
450                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
451         processList.add(used1000);
452         ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
453                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
454         processList.add(used2000);
455         ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
456                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
457         processList.add(used10);
458         ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
459                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
460         processList.add(foregroundAdj);
461         ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
462                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
463         processList.add(serviceAdj);
464         ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
465                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
466         processList.add(systemAdj);
467         list.setLruProcessServiceStartLSP(processList.size());
468 
469         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
470 
471         // 6 processes, only 3 in eligible for cache, so only those are re-ranked.
472         assertThat(processList).containsExactly(used10, used1000, used2000,
473                 foregroundAdj, serviceAdj, systemAdj).inOrder();
474     }
475 
476     @Test
reRankLruCachedApps_fewNonServiceProcesses()477     public void reRankLruCachedApps_fewNonServiceProcesses() throws InterruptedException {
478         setConfig(/* numberToReRank= */ 4,
479                 /* preserveTopNApps= */ 0,
480                 /* useFrequentRss= */ true,
481                 /* rssUpdateRateMs= */ 0,
482                 /* usesWeight= */ 1.0f,
483                 /* pssWeight= */ 0.0f,
484                 /* lruWeight= */ 0.0f);
485 
486         ProcessList list = new ProcessList();
487         ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
488         ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
489                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
490         processList.add(used1000);
491         ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
492                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
493         processList.add(used2000);
494         ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
495                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
496         processList.add(used10);
497         ProcessRecord service1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
498                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
499         processList.add(service1);
500         ProcessRecord service2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
501                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
502         processList.add(service2);
503         ProcessRecord service3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
504                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
505         processList.add(service3);
506         list.setLruProcessServiceStartLSP(3);
507 
508         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
509 
510         // Services unchanged, rest re-ranked.
511         assertThat(processList).containsExactly(used10, used1000, used2000, service1, service2,
512                 service3).inOrder();
513     }
514 
515     @Test
reRankLruCachedApps_manyProcessesThenFew()516     public void reRankLruCachedApps_manyProcessesThenFew() throws InterruptedException {
517         setConfig(/* numberToReRank= */ 6,
518                 /* preserveTopNApps= */ 0,
519                 /* useFrequentRss= */ true,
520                 /* rssUpdateRateMs= */ 0,
521                 /* usesWeight= */ 1.0f,
522                 /* pssWeight= */ 0.0f,
523                 /* lruWeight= */ 0.0f);
524 
525         ProcessList set1List = new ProcessList();
526         ArrayList<ProcessRecord> set1ProcessList = set1List.getLruProcessesLSP();
527         ProcessRecord set1Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
528                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
529         set1ProcessList.add(set1Used1000);
530         ProcessRecord set1Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
531                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
532         set1ProcessList.add(set1Used2000);
533         ProcessRecord set1Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
534                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
535         set1ProcessList.add(set1Used10);
536         ProcessRecord set1Uses20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
537                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
538         set1ProcessList.add(set1Uses20);
539         ProcessRecord set1Uses500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
540                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
541         set1ProcessList.add(set1Uses500);
542         ProcessRecord set1Uses200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
543                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
544         set1ProcessList.add(set1Uses200);
545         set1List.setLruProcessServiceStartLSP(set1ProcessList.size());
546 
547         mCacheOomRanker.reRankLruCachedAppsLSP(set1ProcessList,
548                 set1List.getLruProcessServiceStartLOSP());
549         assertThat(set1ProcessList).containsExactly(set1Used10, set1Uses20, set1Uses200,
550                 set1Uses500, set1Used1000, set1Used2000).inOrder();
551 
552         ProcessList set2List = new ProcessList();
553         ArrayList<ProcessRecord> set2ProcessList = set2List.getLruProcessesLSP();
554         ProcessRecord set2Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
555                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
556         set2ProcessList.add(set2Used1000);
557         ProcessRecord set2Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
558                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
559         set2ProcessList.add(set2Used2000);
560         ProcessRecord set2Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
561                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
562         set2ProcessList.add(set2Used10);
563         ProcessRecord set2ForegroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
564                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
565         set2ProcessList.add(set2ForegroundAdj);
566         ProcessRecord set2ServiceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
567                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
568         set2ProcessList.add(set2ServiceAdj);
569         ProcessRecord set2SystemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
570                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
571         set2ProcessList.add(set2SystemAdj);
572         set2List.setLruProcessServiceStartLSP(set2ProcessList.size());
573 
574         mCacheOomRanker.reRankLruCachedAppsLSP(set2ProcessList,
575                 set2List.getLruProcessServiceStartLOSP());
576         assertThat(set2ProcessList).containsExactly(set2Used10, set2Used1000, set2Used2000,
577                 set2ForegroundAdj, set2ServiceAdj, set2SystemAdj).inOrder();
578     }
579 
580     @Test
reRankLruCachedApps_preservesTopNApps()581     public void reRankLruCachedApps_preservesTopNApps() throws InterruptedException {
582         setConfig(/* numberToReRank= */ 6,
583                 /* preserveTopNApps= */ 3,
584                 /* useFrequentRss= */ true,
585                 /* rssUpdateRateMs= */ 0,
586                 /* usesWeight= */ 1.0f,
587                 /* pssWeight= */ 0.0f,
588                 /* lruWeight= */ 0.0f);
589 
590         ProcessList list = new ProcessList();
591         ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
592         ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
593                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
594         processList.add(used1000);
595         ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
596                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
597         processList.add(used2000);
598         ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
599                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
600         processList.add(used10);
601         // Preserving the top 3 processes, so these should not be re-ranked.
602         ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
603                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
604         processList.add(used20);
605         ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
606                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
607         processList.add(used500);
608         ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
609                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
610         processList.add(used200);
611         list.setLruProcessServiceStartLSP(processList.size());
612 
613         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
614 
615         // First 3 ordered by uses, then last processes position unchanged.
616         assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500,
617                 used200).inOrder();
618     }
619 
620     @Test
reRankLruCachedApps_preservesTopNApps_allAppsUnchanged()621     public void reRankLruCachedApps_preservesTopNApps_allAppsUnchanged()
622             throws InterruptedException {
623         setConfig(/* numberToReRank= */ 6,
624                 /* preserveTopNApps= */ 100,
625                 /* useFrequentRss= */ true,
626                 /* rssUpdateRateMs= */ 0,
627                 /* usesWeight= */ 1.0f,
628                 /* pssWeight= */ 0.0f,
629                 /* lruWeight= */ 0.0f);
630 
631         ProcessList list = new ProcessList();
632         ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
633         ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
634                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
635         processList.add(used1000);
636         ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
637                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
638         processList.add(used2000);
639         ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
640                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
641         processList.add(used10);
642         ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
643                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
644         processList.add(used20);
645         ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
646                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
647         processList.add(used500);
648         ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
649                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
650         processList.add(used200);
651         list.setLruProcessServiceStartLSP(processList.size());
652 
653         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
654 
655         // Nothing reordered, as we preserve the top 100 apps.
656         assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500,
657                 used200).inOrder();
658     }
659 
660     @Test
reRankLruCachedApps_preservesTopNApps_negativeReplacedWithDefault()661     public void reRankLruCachedApps_preservesTopNApps_negativeReplacedWithDefault()
662             throws InterruptedException {
663         setConfig(/* numberToReRank= */ 6,
664                 /* preserveTopNApps= */ -100,
665                 /* useFrequentRss= */ true,
666                 /* rssUpdateRateMs= */ 0,
667                 /* usesWeight= */ 1.0f,
668                 /* pssWeight= */ 0.0f,
669                 /* lruWeight= */ 0.0f);
670 
671         ProcessList list = new ProcessList();
672         ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
673         ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
674                 NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
675         processList.add(used1000);
676         ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
677                 NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
678         processList.add(used2000);
679         ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
680                 NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
681         processList.add(used10);
682         // Negative preserveTopNApps interpreted as the default (3), so the last three are unranked.
683         ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
684                 NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
685         processList.add(used20);
686         ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
687                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
688         processList.add(used500);
689         ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
690                 NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
691         processList.add(used200);
692         list.setLruProcessServiceStartLSP(processList.size());
693 
694         mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
695 
696         // First 3 apps re-ranked, as preserveTopNApps is interpreted as 3.
697         assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500,
698                 used200).inOrder();
699     }
700 
setConfig(int numberToReRank, int preserveTopNApps, boolean useFrequentRss, long rssUpdateRateMs, float usesWeight, float pssWeight, float lruWeight)701     private void setConfig(int numberToReRank, int preserveTopNApps, boolean useFrequentRss,
702             long rssUpdateRateMs, float usesWeight, float pssWeight, float lruWeight)
703             throws InterruptedException {
704         mExecutor.init(4);
705         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
706                 CacheOomRanker.KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK,
707                 Integer.toString(numberToReRank),
708                 false);
709         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
710                 CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS,
711                 Integer.toString(preserveTopNApps),
712                 false);
713         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
714                 CacheOomRanker.KEY_OOM_RE_RANKING_USE_FREQUENT_RSS,
715                 Boolean.toString(useFrequentRss),
716                 false);
717         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
718                 CacheOomRanker.KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS,
719                 Long.toString(rssUpdateRateMs),
720                 false);
721         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
722                 CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT,
723                 Float.toString(lruWeight),
724                 false);
725         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
726                 CacheOomRanker.KEY_OOM_RE_RANKING_RSS_WEIGHT,
727                 Float.toString(pssWeight),
728                 false);
729         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
730                 CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT,
731                 Float.toString(usesWeight),
732                 false);
733         mExecutor.waitForLatch();
734         assertThat(mCacheOomRanker.getNumberToReRank()).isEqualTo(numberToReRank);
735         assertThat(mCacheOomRanker.mUseFrequentRss).isEqualTo(useFrequentRss);
736         assertThat(mCacheOomRanker.mRssUpdateRateMs).isEqualTo(rssUpdateRateMs);
737         assertThat(mCacheOomRanker.mRssWeight).isEqualTo(pssWeight);
738         assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(usesWeight);
739         assertThat(mCacheOomRanker.mLruWeight).isEqualTo(lruWeight);
740     }
741 
nextProcessRecord(int setAdj, long lastActivityTime, long lastRss, int wentToForegroundCount)742     private ProcessRecord nextProcessRecord(int setAdj, long lastActivityTime, long lastRss,
743             int wentToForegroundCount) {
744         ApplicationInfo ai = new ApplicationInfo();
745         ai.packageName = "a.package.name" + mNextPackageName++;
746         ProcessRecord app = new ProcessRecord(mAms, ai, ai.packageName + ":process", mNextUid++);
747         app.setPid(mNextPid++);
748         app.info.uid = mNextPackageUid++;
749         // Exact value does not mater, it can be any state for which compaction is allowed.
750         app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
751         app.mState.setCurAdj(setAdj);
752         app.setLastActivityTime(lastActivityTime);
753         mPidToRss.put(app.getPid(), lastRss);
754         for (int i = 0; i < wentToForegroundCount; ++i) {
755             app.mState.setSetProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
756             app.mState.setSetProcState(ActivityManager.PROCESS_STATE_CACHED_RECENT);
757         }
758         // Sets the thread returned by ProcessRecord#getThread, which we use to check whether the
759         // app is currently launching.
760         ProcessStatsService processStatsService = new ProcessStatsService(
761                 mock(ActivityManagerService.class), new File(Environment.getDataSystemCeDirectory(),
762                 "procstats"));
763         app.makeActive(mock(IApplicationThread.class), processStatsService);
764         return app;
765     }
766 
767     private class TestExecutor implements Executor {
768         private CountDownLatch mLatch;
769 
init(int count)770         private void init(int count) {
771             mLatch = new CountDownLatch(count);
772         }
773 
init()774         private void init() {
775             init(1);
776         }
777 
waitForLatch()778         private void waitForLatch() throws InterruptedException {
779             mLatch.await(5, TimeUnit.SECONDS);
780         }
781 
782         @Override
execute(Runnable command)783         public void execute(Runnable command) {
784             command.run();
785             mLatch.countDown();
786         }
787     }
788 
789     private class TestInjector extends ActivityManagerService.Injector {
TestInjector(Context context)790         private TestInjector(Context context) {
791             super(context);
792         }
793 
794         @Override
getAppOpsService(File recentAccessesFile, File storageFile, Handler handler)795         public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
796                 Handler handler) {
797             return mAppOpsService;
798         }
799 
800         @Override
getUiHandler(ActivityManagerService service)801         public Handler getUiHandler(ActivityManagerService service) {
802             return mHandler;
803         }
804     }
805 
806     // TODO: [b/302724778] Remove manual JNI load
807     static {
808         System.loadLibrary("mockingservicestestjni");
809     }
810 }
811