1 /*
2  * Copyright (C) 2018 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.os.Process.THREAD_PRIORITY_FOREGROUND;
20 
21 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
22 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
23 
24 import android.app.ActivityManager;
25 import android.app.ActivityThread;
26 import android.os.Debug;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.os.Process;
30 import android.os.SystemClock;
31 import android.os.Trace;
32 import android.provider.DeviceConfig;
33 import android.provider.DeviceConfig.OnPropertiesChangedListener;
34 import android.provider.DeviceConfig.Properties;
35 import android.text.TextUtils;
36 import android.util.EventLog;
37 import android.util.Slog;
38 import android.util.StatsLog;
39 
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.server.ServiceThread;
43 
44 import java.io.FileOutputStream;
45 import java.io.PrintWriter;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.HashSet;
49 import java.util.LinkedHashMap;
50 import java.util.Map;
51 import java.util.Random;
52 import java.util.Set;
53 
54 public final class AppCompactor {
55 
56     // Flags stored in the DeviceConfig API.
57     @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
58     @VisibleForTesting static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
59     @VisibleForTesting static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
60     @VisibleForTesting static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
61     @VisibleForTesting static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
62     @VisibleForTesting static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
63     @VisibleForTesting static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
64     @VisibleForTesting static final String KEY_COMPACT_THROTTLE_5 = "compact_throttle_5";
65     @VisibleForTesting static final String KEY_COMPACT_THROTTLE_6 = "compact_throttle_6";
66     @VisibleForTesting static final String KEY_COMPACT_STATSD_SAMPLE_RATE =
67             "compact_statsd_sample_rate";
68     @VisibleForTesting static final String KEY_COMPACT_FULL_RSS_THROTTLE_KB =
69             "compact_full_rss_throttle_kb";
70     @VisibleForTesting static final String KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB =
71             "compact_full_delta_rss_throttle_kb";
72     @VisibleForTesting static final String KEY_COMPACT_PROC_STATE_THROTTLE =
73             "compact_proc_state_throttle";
74 
75     // Phenotype sends int configurations and we map them to the strings we'll use on device,
76     // preventing a weird string value entering the kernel.
77     private static final int COMPACT_ACTION_FILE_FLAG = 1;
78     private static final int COMPACT_ACTION_ANON_FLAG = 2;
79     private static final int COMPACT_ACTION_FULL_FLAG = 3;
80     private static final int COMPACT_ACTION_NONE_FLAG = 4;
81     private static final String COMPACT_ACTION_NONE = "";
82     private static final String COMPACT_ACTION_FILE = "file";
83     private static final String COMPACT_ACTION_ANON = "anon";
84     private static final String COMPACT_ACTION_FULL = "all";
85 
86     // Defaults for phenotype flags.
87     @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
88     @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
89     @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;
90     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
91     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
92     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
93     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000;
94     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_5 = 10 * 60 * 1000;
95     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000;
96     // The sampling rate to push app compaction events into statsd for upload.
97     @VisibleForTesting static final float DEFAULT_STATSD_SAMPLE_RATE = 0.1f;
98     @VisibleForTesting static final long DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB = 12_000L;
99     @VisibleForTesting static final long DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB = 8_000L;
100     // Format of this string should be a comma separated list of integers.
101     @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
102             String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
103 
104     @VisibleForTesting
105     interface PropertyChangedCallbackForTest {
onPropertyChanged()106         void onPropertyChanged();
107     }
108     private PropertyChangedCallbackForTest mTestCallback;
109 
110     // Handler constants.
111     static final int COMPACT_PROCESS_SOME = 1;
112     static final int COMPACT_PROCESS_FULL = 2;
113     static final int COMPACT_PROCESS_PERSISTENT = 3;
114     static final int COMPACT_PROCESS_BFGS = 4;
115     static final int COMPACT_PROCESS_MSG = 1;
116     static final int COMPACT_SYSTEM_MSG = 2;
117 
118     /**
119      * This thread must be moved to the system background cpuset.
120      * If that doesn't happen, it's probably going to draw a lot of power.
121      * However, this has to happen after the first updateOomAdjLocked, because
122      * that will wipe out the cpuset assignment for system_server threads.
123      * Accordingly, this is in the AMS constructor.
124      */
125     final ServiceThread mCompactionThread;
126 
127     private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
128             new ArrayList<ProcessRecord>();
129     private final ActivityManagerService mAm;
130     private final OnPropertiesChangedListener mOnFlagsChangedListener =
131             new OnPropertiesChangedListener() {
132                 @Override
133                 public void onPropertiesChanged(Properties properties) {
134                     synchronized (mPhenotypeFlagLock) {
135                         for (String name : properties.getKeyset()) {
136                             if (KEY_USE_COMPACTION.equals(name)) {
137                                 updateUseCompaction();
138                             } else if (KEY_COMPACT_ACTION_1.equals(name)
139                                     || KEY_COMPACT_ACTION_2.equals(name)) {
140                                 updateCompactionActions();
141                             } else if (KEY_COMPACT_THROTTLE_1.equals(name)
142                                     || KEY_COMPACT_THROTTLE_2.equals(name)
143                                     || KEY_COMPACT_THROTTLE_3.equals(name)
144                                     || KEY_COMPACT_THROTTLE_4.equals(name)) {
145                                 updateCompactionThrottles();
146                             } else if (KEY_COMPACT_STATSD_SAMPLE_RATE.equals(name)) {
147                                 updateStatsdSampleRate();
148                             } else if (KEY_COMPACT_FULL_RSS_THROTTLE_KB.equals(name)) {
149                                 updateFullRssThrottle();
150                             } else if (KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB.equals(name)) {
151                                 updateFullDeltaRssThrottle();
152                             } else if (KEY_COMPACT_PROC_STATE_THROTTLE.equals(name)) {
153                                 updateProcStateThrottle();
154                             }
155                         }
156                     }
157                     if (mTestCallback != null) {
158                         mTestCallback.onPropertyChanged();
159                     }
160                 }
161             };
162 
163     private final Object mPhenotypeFlagLock = new Object();
164 
165     // Configured by phenotype. Updates from the server take effect immediately.
166     @GuardedBy("mPhenotypeFlagLock")
167     @VisibleForTesting volatile String mCompactActionSome =
168             compactActionIntToString(DEFAULT_COMPACT_ACTION_1);
169     @GuardedBy("mPhenotypeFlagLock")
170     @VisibleForTesting volatile String mCompactActionFull =
171             compactActionIntToString(DEFAULT_COMPACT_ACTION_2);
172     @GuardedBy("mPhenotypeFlagLock")
173     @VisibleForTesting volatile long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
174     @GuardedBy("mPhenotypeFlagLock")
175     @VisibleForTesting volatile long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
176     @GuardedBy("mPhenotypeFlagLock")
177     @VisibleForTesting volatile long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
178     @GuardedBy("mPhenotypeFlagLock")
179     @VisibleForTesting volatile long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
180     @GuardedBy("mPhenotypeFlagLock")
181     @VisibleForTesting volatile long mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
182     @GuardedBy("mPhenotypeFlagLock")
183     @VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
184     @GuardedBy("mPhenotypeFlagLock")
185     private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION;
186     private final Random mRandom = new Random();
187     @GuardedBy("mPhenotypeFlagLock")
188     @VisibleForTesting volatile float mStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE;
189     @GuardedBy("mPhenotypeFlagLock")
190     @VisibleForTesting volatile long mFullAnonRssThrottleKb =
191             DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
192     @GuardedBy("mPhenoypeFlagLock")
193     @VisibleForTesting volatile long mFullDeltaRssThrottleKb =
194             DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
195     @GuardedBy("mPhenoypeFlagLock")
196     @VisibleForTesting final Set<Integer> mProcStateThrottle;
197 
198     // Handler on which compaction runs.
199     private Handler mCompactionHandler;
200 
201     // Maps process ID to last compaction statistics for processes that we've fully compacted. Used
202     // when evaluating throttles that we only consider for "full" compaction, so we don't store
203     // data for "some" compactions.
204     private Map<Integer, LastCompactionStats> mLastCompactionStats =
205             new LinkedHashMap<Integer, LastCompactionStats>() {
206                 @Override
207                 protected boolean removeEldestEntry(Map.Entry eldest) {
208                     return size() > 100;
209                 }
210     };
211 
212     private int mSomeCompactionCount;
213     private int mFullCompactionCount;
214     private int mPersistentCompactionCount;
215     private int mBfgsCompactionCount;
216 
AppCompactor(ActivityManagerService am)217     public AppCompactor(ActivityManagerService am) {
218         mAm = am;
219         mCompactionThread = new ServiceThread("CompactionThread",
220                 THREAD_PRIORITY_FOREGROUND, true);
221         mProcStateThrottle = new HashSet<>();
222     }
223 
224     @VisibleForTesting
AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback)225     AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
226         this(am);
227         mTestCallback = callback;
228     }
229 
230     /**
231      * Reads phenotype config to determine whether app compaction is enabled or not and
232      * starts the background thread if necessary.
233      */
init()234     public void init() {
235         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
236                 ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
237         synchronized (mPhenotypeFlagLock) {
238             updateUseCompaction();
239             updateCompactionActions();
240             updateCompactionThrottles();
241             updateStatsdSampleRate();
242             updateFullRssThrottle();
243             updateFullDeltaRssThrottle();
244             updateProcStateThrottle();
245         }
246         Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
247                 Process.THREAD_GROUP_SYSTEM);
248     }
249 
250     /**
251      * Returns whether compaction is enabled.
252      */
useCompaction()253     public boolean useCompaction() {
254         synchronized (mPhenotypeFlagLock) {
255             return mUseCompaction;
256         }
257     }
258 
259     @GuardedBy("mAm")
dump(PrintWriter pw)260     void dump(PrintWriter pw) {
261         pw.println("AppCompactor settings");
262         synchronized (mPhenotypeFlagLock) {
263             pw.println("  " + KEY_USE_COMPACTION + "=" + mUseCompaction);
264             pw.println("  " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
265             pw.println("  " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull);
266             pw.println("  " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome);
267             pw.println("  " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
268             pw.println("  " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
269             pw.println("  " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
270             pw.println("  " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS);
271             pw.println("  " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent);
272             pw.println("  " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mStatsdSampleRate);
273             pw.println("  " + KEY_COMPACT_FULL_RSS_THROTTLE_KB + "="
274                     + mFullAnonRssThrottleKb);
275             pw.println("  " + KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + "="
276                     + mFullDeltaRssThrottleKb);
277             pw.println("  "  + KEY_COMPACT_PROC_STATE_THROTTLE + "="
278                     + Arrays.toString(mProcStateThrottle.toArray(new Integer[0])));
279 
280             pw.println("  " + mSomeCompactionCount + " some, " + mFullCompactionCount
281                     + " full, " + mPersistentCompactionCount + " persistent, "
282                     + mBfgsCompactionCount + " BFGS compactions.");
283 
284             pw.println("  Tracking last compaction stats for " + mLastCompactionStats.size()
285                     + " processes.");
286             if (DEBUG_COMPACTION) {
287                 for (Map.Entry<Integer, LastCompactionStats> entry
288                         : mLastCompactionStats.entrySet()) {
289                     int pid = entry.getKey();
290                     LastCompactionStats stats = entry.getValue();
291                     pw.println("    " + pid + ": "
292                             + Arrays.toString(stats.getRssAfterCompaction()));
293                 }
294             }
295         }
296     }
297 
298     @GuardedBy("mAm")
compactAppSome(ProcessRecord app)299     void compactAppSome(ProcessRecord app) {
300         app.reqCompactAction = COMPACT_PROCESS_SOME;
301         mPendingCompactionProcesses.add(app);
302         mCompactionHandler.sendMessage(
303             mCompactionHandler.obtainMessage(
304                 COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
305     }
306 
307     @GuardedBy("mAm")
compactAppFull(ProcessRecord app)308     void compactAppFull(ProcessRecord app) {
309         app.reqCompactAction = COMPACT_PROCESS_FULL;
310         mPendingCompactionProcesses.add(app);
311         mCompactionHandler.sendMessage(
312             mCompactionHandler.obtainMessage(
313                 COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
314 
315     }
316 
317     @GuardedBy("mAm")
compactAppPersistent(ProcessRecord app)318     void compactAppPersistent(ProcessRecord app) {
319         app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
320         mPendingCompactionProcesses.add(app);
321         mCompactionHandler.sendMessage(
322                 mCompactionHandler.obtainMessage(
323                     COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
324     }
325 
326     @GuardedBy("mAm")
shouldCompactPersistent(ProcessRecord app, long now)327     boolean shouldCompactPersistent(ProcessRecord app, long now) {
328         return (app.lastCompactTime == 0
329                 || (now - app.lastCompactTime) > mCompactThrottlePersistent);
330     }
331 
332     @GuardedBy("mAm")
compactAppBfgs(ProcessRecord app)333     void compactAppBfgs(ProcessRecord app) {
334         app.reqCompactAction = COMPACT_PROCESS_BFGS;
335         mPendingCompactionProcesses.add(app);
336         mCompactionHandler.sendMessage(
337                 mCompactionHandler.obtainMessage(
338                     COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
339     }
340 
341     @GuardedBy("mAm")
shouldCompactBFGS(ProcessRecord app, long now)342     boolean shouldCompactBFGS(ProcessRecord app, long now) {
343         return (app.lastCompactTime == 0
344                 || (now - app.lastCompactTime) > mCompactThrottleBFGS);
345     }
346 
347     @GuardedBy("mAm")
compactAllSystem()348     void compactAllSystem() {
349         if (mUseCompaction) {
350             mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
351                                               COMPACT_SYSTEM_MSG));
352         }
353     }
354 
compactSystem()355     private native void compactSystem();
356 
357     /**
358      * Reads the flag value from DeviceConfig to determine whether app compaction
359      * should be enabled, and starts the compaction thread if needed.
360      */
361     @GuardedBy("mPhenotypeFlagLock")
updateUseCompaction()362     private void updateUseCompaction() {
363         mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
364                     KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
365         if (mUseCompaction && !mCompactionThread.isAlive()) {
366             mCompactionThread.start();
367             mCompactionHandler = new MemCompactionHandler();
368         }
369     }
370 
371     @GuardedBy("mPhenotypeFlagLock")
updateCompactionActions()372     private void updateCompactionActions() {
373         int compactAction1 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
374                 KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
375 
376         int compactAction2 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
377                 KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
378 
379         mCompactActionSome = compactActionIntToString(compactAction1);
380         mCompactActionFull = compactActionIntToString(compactAction2);
381     }
382 
383     @GuardedBy("mPhenotypeFlagLock")
updateCompactionThrottles()384     private void updateCompactionThrottles() {
385         boolean useThrottleDefaults = false;
386         String throttleSomeSomeFlag =
387                 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
388                     KEY_COMPACT_THROTTLE_1);
389         String throttleSomeFullFlag =
390                 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
391                     KEY_COMPACT_THROTTLE_2);
392         String throttleFullSomeFlag =
393                 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
394                     KEY_COMPACT_THROTTLE_3);
395         String throttleFullFullFlag =
396                 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
397                     KEY_COMPACT_THROTTLE_4);
398         String throttleBFGSFlag =
399                 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
400                     KEY_COMPACT_THROTTLE_5);
401         String throttlePersistentFlag =
402                 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
403                     KEY_COMPACT_THROTTLE_6);
404 
405         if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
406                 || TextUtils.isEmpty(throttleFullSomeFlag)
407                 || TextUtils.isEmpty(throttleFullFullFlag)
408                 || TextUtils.isEmpty(throttleBFGSFlag)
409                 || TextUtils.isEmpty(throttlePersistentFlag)) {
410             // Set defaults for all if any are not set.
411             useThrottleDefaults = true;
412         } else {
413             try {
414                 mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag);
415                 mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
416                 mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
417                 mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
418                 mCompactThrottleBFGS = Integer.parseInt(throttleBFGSFlag);
419                 mCompactThrottlePersistent = Integer.parseInt(throttlePersistentFlag);
420             } catch (NumberFormatException e) {
421                 useThrottleDefaults = true;
422             }
423         }
424 
425         if (useThrottleDefaults) {
426             mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
427             mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
428             mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
429             mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
430             mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
431             mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
432         }
433     }
434 
435     @GuardedBy("mPhenotypeFlagLock")
updateStatsdSampleRate()436     private void updateStatsdSampleRate() {
437         mStatsdSampleRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
438                 KEY_COMPACT_STATSD_SAMPLE_RATE, DEFAULT_STATSD_SAMPLE_RATE);
439         mStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mStatsdSampleRate));
440     }
441 
442     @GuardedBy("mPhenotypeFlagLock")
updateFullRssThrottle()443     private void updateFullRssThrottle() {
444         mFullAnonRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
445                 KEY_COMPACT_FULL_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
446 
447         // Don't allow negative values. 0 means don't apply the throttle.
448         if (mFullAnonRssThrottleKb < 0) {
449             mFullAnonRssThrottleKb = DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
450         }
451     }
452 
453     @GuardedBy("mPhenotypeFlagLock")
updateFullDeltaRssThrottle()454     private void updateFullDeltaRssThrottle() {
455         mFullDeltaRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
456                 KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
457 
458         if (mFullDeltaRssThrottleKb < 0) {
459             mFullDeltaRssThrottleKb = DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
460         }
461     }
462 
463     @GuardedBy("mPhenotypeFlagLock")
updateProcStateThrottle()464     private void updateProcStateThrottle() {
465         String procStateThrottleString = DeviceConfig.getString(
466                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_PROC_STATE_THROTTLE,
467                 DEFAULT_COMPACT_PROC_STATE_THROTTLE);
468         if (!parseProcStateThrottle(procStateThrottleString)) {
469             Slog.w(TAG_AM, "Unable to parse app compact proc state throttle \""
470                     + procStateThrottleString + "\" falling back to default.");
471             if (!parseProcStateThrottle(DEFAULT_COMPACT_PROC_STATE_THROTTLE)) {
472                 Slog.wtf(TAG_AM,
473                         "Unable to parse default app compact proc state throttle "
474                                 + DEFAULT_COMPACT_PROC_STATE_THROTTLE);
475             }
476         }
477     }
478 
parseProcStateThrottle(String procStateThrottleString)479     private boolean parseProcStateThrottle(String procStateThrottleString) {
480         String[] procStates = TextUtils.split(procStateThrottleString, ",");
481         mProcStateThrottle.clear();
482         for (String procState : procStates) {
483             try {
484                 mProcStateThrottle.add(Integer.parseInt(procState));
485             } catch (NumberFormatException e) {
486                 Slog.e(TAG_AM, "Failed to parse default app compaction proc state: "
487                         + procState);
488                 return false;
489             }
490         }
491         return true;
492     }
493 
494     @VisibleForTesting
compactActionIntToString(int action)495     static String compactActionIntToString(int action) {
496         switch(action) {
497             case COMPACT_ACTION_NONE_FLAG:
498                 return COMPACT_ACTION_NONE;
499             case COMPACT_ACTION_FILE_FLAG:
500                 return COMPACT_ACTION_FILE;
501             case COMPACT_ACTION_ANON_FLAG:
502                 return COMPACT_ACTION_ANON;
503             case COMPACT_ACTION_FULL_FLAG:
504                 return COMPACT_ACTION_FULL;
505             default:
506                 return COMPACT_ACTION_NONE;
507         }
508     }
509 
510     private static final class LastCompactionStats {
511         private final long[] mRssAfterCompaction;
512 
LastCompactionStats(long[] rss)513         LastCompactionStats(long[] rss) {
514             mRssAfterCompaction = rss;
515         }
516 
getRssAfterCompaction()517         long[] getRssAfterCompaction() {
518             return mRssAfterCompaction;
519         }
520     }
521 
522     private final class MemCompactionHandler extends Handler {
MemCompactionHandler()523         private MemCompactionHandler() {
524             super(mCompactionThread.getLooper());
525         }
526 
527         @Override
handleMessage(Message msg)528         public void handleMessage(Message msg) {
529             switch (msg.what) {
530                 case COMPACT_PROCESS_MSG: {
531                     long start = SystemClock.uptimeMillis();
532                     ProcessRecord proc;
533                     int pid;
534                     String action;
535                     final String name;
536                     int pendingAction, lastCompactAction;
537                     long lastCompactTime;
538                     LastCompactionStats lastCompactionStats;
539                     int lastOomAdj = msg.arg1;
540                     int procState = msg.arg2;
541                     synchronized (mAm) {
542                         proc = mPendingCompactionProcesses.remove(0);
543 
544                         pendingAction = proc.reqCompactAction;
545                         pid = proc.pid;
546                         name = proc.processName;
547 
548                         // don't compact if the process has returned to perceptible
549                         // and this is only a cached/home/prev compaction
550                         if ((pendingAction == COMPACT_PROCESS_SOME
551                                 || pendingAction == COMPACT_PROCESS_FULL)
552                                 && (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
553                             if (DEBUG_COMPACTION) {
554                                 Slog.d(TAG_AM,
555                                         "Skipping compaction as process " + name + " is "
556                                         + "now perceptible.");
557                             }
558                             return;
559                         }
560 
561                         lastCompactAction = proc.lastCompactAction;
562                         lastCompactTime = proc.lastCompactTime;
563                         // remove rather than get so that insertion order will be updated when we
564                         // put the post-compaction stats back into the map.
565                         lastCompactionStats = mLastCompactionStats.remove(pid);
566                     }
567 
568                     if (pid == 0) {
569                         // not a real process, either one being launched or one being killed
570                         return;
571                     }
572 
573                     // basic throttling
574                     // use the Phenotype flag knobs to determine whether current/prevous
575                     // compaction combo should be throtted or not
576 
577                     // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
578                     // should very seldom change, and taking the risk of using the wrong action is
579                     // preferable to taking the lock for every single compaction action.
580                     if (lastCompactTime != 0) {
581                         if (pendingAction == COMPACT_PROCESS_SOME) {
582                             if ((lastCompactAction == COMPACT_PROCESS_SOME
583                                     && (start - lastCompactTime < mCompactThrottleSomeSome))
584                                     || (lastCompactAction == COMPACT_PROCESS_FULL
585                                         && (start - lastCompactTime
586                                                 < mCompactThrottleSomeFull))) {
587                                 if (DEBUG_COMPACTION) {
588                                     Slog.d(TAG_AM, "Skipping some compaction for " + name
589                                             + ": too soon. throttle=" + mCompactThrottleSomeSome
590                                             + "/" + mCompactThrottleSomeFull + " last="
591                                             + (start - lastCompactTime) + "ms ago");
592                                 }
593                                 return;
594                             }
595                         } else if (pendingAction == COMPACT_PROCESS_FULL) {
596                             if ((lastCompactAction == COMPACT_PROCESS_SOME
597                                     && (start - lastCompactTime < mCompactThrottleFullSome))
598                                     || (lastCompactAction == COMPACT_PROCESS_FULL
599                                         && (start - lastCompactTime
600                                                 < mCompactThrottleFullFull))) {
601                                 if (DEBUG_COMPACTION) {
602                                     Slog.d(TAG_AM, "Skipping full compaction for " + name
603                                             + ": too soon. throttle=" + mCompactThrottleFullSome
604                                             + "/" + mCompactThrottleFullFull + " last="
605                                             + (start - lastCompactTime) + "ms ago");
606                                 }
607                                 return;
608                             }
609                         } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
610                             if (start - lastCompactTime < mCompactThrottlePersistent) {
611                                 if (DEBUG_COMPACTION) {
612                                     Slog.d(TAG_AM, "Skipping persistent compaction for " + name
613                                             + ": too soon. throttle=" + mCompactThrottlePersistent
614                                             + " last=" + (start - lastCompactTime) + "ms ago");
615                                 }
616                                 return;
617                             }
618                         } else if (pendingAction == COMPACT_PROCESS_BFGS) {
619                             if (start - lastCompactTime < mCompactThrottleBFGS) {
620                                 if (DEBUG_COMPACTION) {
621                                     Slog.d(TAG_AM, "Skipping bfgs compaction for " + name
622                                             + ": too soon. throttle=" + mCompactThrottleBFGS
623                                             + " last=" + (start - lastCompactTime) + "ms ago");
624                                 }
625                                 return;
626                             }
627                         }
628                     }
629 
630                     switch (pendingAction) {
631                         case COMPACT_PROCESS_SOME:
632                             action = mCompactActionSome;
633                             break;
634                         // For the time being, treat these as equivalent.
635                         case COMPACT_PROCESS_FULL:
636                         case COMPACT_PROCESS_PERSISTENT:
637                         case COMPACT_PROCESS_BFGS:
638                             action = mCompactActionFull;
639                             break;
640                         default:
641                             action = COMPACT_ACTION_NONE;
642                             break;
643                     }
644 
645                     if (COMPACT_ACTION_NONE.equals(action)) {
646                         return;
647                     }
648 
649                     if (mProcStateThrottle.contains(procState)) {
650                         if (DEBUG_COMPACTION) {
651                             Slog.d(TAG_AM, "Skipping full compaction for process " + name
652                                     + "; proc state is " + procState);
653                         }
654                         return;
655                     }
656 
657                     long[] rssBefore = Process.getRss(pid);
658                     long anonRssBefore = rssBefore[2];
659 
660                     if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
661                             && rssBefore[3] == 0) {
662                         if (DEBUG_COMPACTION) {
663                             Slog.d(TAG_AM, "Skipping compaction for" + "process " + pid
664                                     + " with no memory usage. Dead?");
665                         }
666                         return;
667                     }
668 
669                     if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) {
670                         if (mFullAnonRssThrottleKb > 0L
671                                 && anonRssBefore < mFullAnonRssThrottleKb) {
672                             if (DEBUG_COMPACTION) {
673                                 Slog.d(TAG_AM, "Skipping full compaction for process "
674                                         + name + "; anon RSS is too small: " + anonRssBefore
675                                         + "KB.");
676                             }
677                             return;
678                         }
679 
680                         if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
681                             long[] lastRss = lastCompactionStats.getRssAfterCompaction();
682                             long absDelta = Math.abs(rssBefore[1] - lastRss[1])
683                                     + Math.abs(rssBefore[2] - lastRss[2])
684                                     + Math.abs(rssBefore[3] - lastRss[3]);
685                             if (absDelta <= mFullDeltaRssThrottleKb) {
686                                 if (DEBUG_COMPACTION) {
687                                     Slog.d(TAG_AM, "Skipping full compaction for process "
688                                             + name + "; abs delta is too small: " + absDelta
689                                             + "KB.");
690                                 }
691                                 return;
692                             }
693                         }
694                     }
695 
696                     // Now we've passed through all the throttles and are going to compact, update
697                     // bookkeeping.
698                     switch (pendingAction) {
699                         case COMPACT_PROCESS_SOME:
700                             mSomeCompactionCount++;
701                             break;
702                         case COMPACT_PROCESS_FULL:
703                             mFullCompactionCount++;
704                             break;
705                         case COMPACT_PROCESS_PERSISTENT:
706                             mPersistentCompactionCount++;
707                             break;
708                         case COMPACT_PROCESS_BFGS:
709                             mBfgsCompactionCount++;
710                             break;
711                         default:
712                             break;
713                     }
714 
715                     try {
716                         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
717                                 + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
718                                 + ": " + name);
719                         long zramFreeKbBefore = Debug.getZramFreeKb();
720                         FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
721                         fos.write(action.getBytes());
722                         fos.close();
723                         long[] rssAfter = Process.getRss(pid);
724                         long end = SystemClock.uptimeMillis();
725                         long time = end - start;
726                         long zramFreeKbAfter = Debug.getZramFreeKb();
727                         EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
728                                 rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
729                                 rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1],
730                                 rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
731                                 lastCompactAction, lastCompactTime, lastOomAdj, procState,
732                                 zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
733 
734                         // Note that as above not taking mPhenoTypeFlagLock here to avoid locking
735                         // on every single compaction for a flag that will seldom change and the
736                         // impact of reading the wrong value here is low.
737                         if (mRandom.nextFloat() < mStatsdSampleRate) {
738                             StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
739                                     rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
740                                     rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
741                                     lastCompactAction, lastCompactTime, lastOomAdj,
742                                     ActivityManager.processStateAmToProto(procState),
743                                     zramFreeKbBefore, zramFreeKbAfter);
744                         }
745 
746                         synchronized (mAm) {
747                             proc.lastCompactTime = end;
748                             proc.lastCompactAction = pendingAction;
749                         }
750 
751                         if (action.equals(COMPACT_ACTION_FULL)
752                                 || action.equals(COMPACT_ACTION_ANON)) {
753                             mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
754                         }
755                     } catch (Exception e) {
756                         // nothing to do, presumably the process died
757                     } finally {
758                         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
759                     }
760                     break;
761                 }
762                 case COMPACT_SYSTEM_MSG: {
763                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
764                     compactSystem();
765                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
766                     break;
767                 }
768             }
769         }
770     }
771 }
772