blob: bb55ec3100c0612b461348c8ca4e5b8533cf535f [file] [log] [blame]
Tim Murray1cdbd1e2018-12-18 15:18:33 -08001/*
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
17package com.android.server.am;
18
Ben Murdochc26a5a82019-01-16 10:05:58 +000019import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
20import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1;
21import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2;
22import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1;
23import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2;
24import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3;
25import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4;
26import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION;
Tim Murray1cdbd1e2018-12-18 15:18:33 -080027
28import android.app.ActivityManager;
Ben Murdochc26a5a82019-01-16 10:05:58 +000029import android.app.ActivityThread;
Tim Murray1cdbd1e2018-12-18 15:18:33 -080030import android.os.Handler;
Tim Murray1cdbd1e2018-12-18 15:18:33 -080031import android.os.Message;
32import android.os.Process;
33import android.os.SystemClock;
34import android.os.Trace;
Ben Murdochc26a5a82019-01-16 10:05:58 +000035import android.provider.DeviceConfig;
36import android.provider.DeviceConfig.OnPropertyChangedListener;
37import android.text.TextUtils;
Tim Murray1cdbd1e2018-12-18 15:18:33 -080038import android.util.EventLog;
39import android.util.StatsLog;
40
Ben Murdochc26a5a82019-01-16 10:05:58 +000041import com.android.internal.annotations.GuardedBy;
42import com.android.internal.annotations.VisibleForTesting;
Tim Murray1cdbd1e2018-12-18 15:18:33 -080043import com.android.server.ServiceThread;
44
45import java.io.FileOutputStream;
Ben Murdochc26a5a82019-01-16 10:05:58 +000046import java.io.PrintWriter;
Tim Murray1cdbd1e2018-12-18 15:18:33 -080047import java.util.ArrayList;
48
49public final class AppCompactor {
Tim Murray1cdbd1e2018-12-18 15:18:33 -080050
Ben Murdochc26a5a82019-01-16 10:05:58 +000051 // Phenotype sends int configurations and we map them to the strings we'll use on device,
52 // preventing a weird string value entering the kernel.
53 private static final int COMPACT_ACTION_FILE_FLAG = 1;
54 private static final int COMPACT_ACTION_ANON_FLAG = 2;
55 private static final int COMPACT_ACTION_FULL_FLAG = 3;
56 private static final String COMPACT_ACTION_FILE = "file";
57 private static final String COMPACT_ACTION_ANON = "anon";
58 private static final String COMPACT_ACTION_FULL = "all";
59
60 // Defaults for phenotype flags.
61 @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
62 @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
63 @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;
64 @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
65 @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
66 @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
67 @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000;
68
69 @VisibleForTesting
70 interface PropertyChangedCallbackForTest {
71 void onPropertyChanged();
72 }
73 private PropertyChangedCallbackForTest mTestCallback;
74
75 // Handler constants.
Tim Murraybbcbc2c2018-12-20 13:30:59 -080076 static final int COMPACT_PROCESS_SOME = 1;
77 static final int COMPACT_PROCESS_FULL = 2;
78 static final int COMPACT_PROCESS_MSG = 1;
79
80 /**
Tim Murray1cdbd1e2018-12-18 15:18:33 -080081 * This thread must be moved to the system background cpuset.
82 * If that doesn't happen, it's probably going to draw a lot of power.
83 * However, this has to happen after the first updateOomAdjLocked, because
84 * that will wipe out the cpuset assignment for system_server threads.
85 * Accordingly, this is in the AMS constructor.
86 */
87 final ServiceThread mCompactionThread;
88
Ben Murdochc26a5a82019-01-16 10:05:58 +000089 private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
90 new ArrayList<ProcessRecord>();
91 private final ActivityManagerService mAm;
92 private final OnPropertyChangedListener mOnFlagsChangedListener =
93 new OnPropertyChangedListener() {
94 @Override
95 public void onPropertyChanged(String namespace, String name, String value) {
96 synchronized (mPhenotypeFlagLock) {
97 if (KEY_USE_COMPACTION.equals(name)) {
98 updateUseCompaction();
99 } else if (KEY_COMPACT_ACTION_1.equals(name)
100 || KEY_COMPACT_ACTION_2.equals(name)) {
101 updateCompactionActions();
102 } else if (KEY_COMPACT_THROTTLE_1.equals(name)
103 || KEY_COMPACT_THROTTLE_2.equals(name)
104 || KEY_COMPACT_THROTTLE_3.equals(name)
105 || KEY_COMPACT_THROTTLE_4.equals(name)) {
106 updateCompactionThrottles();
107 }
108 }
109 if (mTestCallback != null) {
110 mTestCallback.onPropertyChanged();
111 }
112 }
113 };
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800114
Ben Murdochc26a5a82019-01-16 10:05:58 +0000115 private final Object mPhenotypeFlagLock = new Object();
Tim Murraybbcbc2c2018-12-20 13:30:59 -0800116
Ben Murdochc26a5a82019-01-16 10:05:58 +0000117 // Configured by phenotype. Updates from the server take effect immediately.
118 @GuardedBy("mPhenotypeFlagLock")
119 @VisibleForTesting String mCompactActionSome =
120 compactActionIntToString(DEFAULT_COMPACT_ACTION_1);
121 @GuardedBy("mPhenotypeFlagLock")
122 @VisibleForTesting String mCompactActionFull =
123 compactActionIntToString(DEFAULT_COMPACT_ACTION_2);
124 @GuardedBy("mPhenotypeFlagLock")
125 @VisibleForTesting long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
126 @GuardedBy("mPhenotypeFlagLock")
127 @VisibleForTesting long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
128 @GuardedBy("mPhenotypeFlagLock")
129 @VisibleForTesting long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
130 @GuardedBy("mPhenotypeFlagLock")
131 @VisibleForTesting long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
132 @GuardedBy("mPhenotypeFlagLock")
133 private boolean mUseCompaction = DEFAULT_USE_COMPACTION;
Tim Murraybbcbc2c2018-12-20 13:30:59 -0800134
Ben Murdochc26a5a82019-01-16 10:05:58 +0000135 // Handler on which compaction runs.
136 private Handler mCompactionHandler;
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800137
138 public AppCompactor(ActivityManagerService am) {
139 mAm = am;
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800140 mCompactionThread = new ServiceThread("CompactionThread",
141 THREAD_PRIORITY_FOREGROUND, true);
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800142 }
143
Ben Murdochc26a5a82019-01-16 10:05:58 +0000144 @VisibleForTesting
145 AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
146 this(am);
147 mTestCallback = callback;
148 }
149
150 /**
151 * Reads phenotype config to determine whether app compaction is enabled or not and
152 * starts the background thread if necessary.
153 */
154 public void init() {
155 DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE,
156 ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
157 synchronized (mPhenotypeFlagLock) {
158 updateUseCompaction();
159 updateCompactionActions();
160 updateCompactionThrottles();
161 }
162 }
163
164 /**
165 * Returns whether compaction is enabled.
166 */
167 public boolean useCompaction() {
168 synchronized (mPhenotypeFlagLock) {
169 return mUseCompaction;
170 }
171 }
172
173 @GuardedBy("mAm")
174 void dump(PrintWriter pw) {
175 pw.println("AppCompactor settings");
176 synchronized (mPhenotypeFlagLock) {
177 pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction);
178 pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
179 pw.println(" " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull);
180 pw.println(" " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome);
181 pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
182 pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
183 pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
184 }
185 }
186
187 @GuardedBy("mAm")
188 void compactAppSome(ProcessRecord app) {
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800189 app.reqCompactAction = COMPACT_PROCESS_SOME;
190 mPendingCompactionProcesses.add(app);
191 mCompactionHandler.sendMessage(
192 mCompactionHandler.obtainMessage(
193 COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
194 }
195
Ben Murdochc26a5a82019-01-16 10:05:58 +0000196 @GuardedBy("mAm")
197 void compactAppFull(ProcessRecord app) {
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800198 app.reqCompactAction = COMPACT_PROCESS_FULL;
199 mPendingCompactionProcesses.add(app);
200 mCompactionHandler.sendMessage(
201 mCompactionHandler.obtainMessage(
202 COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
203
204 }
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800205
Ben Murdochc26a5a82019-01-16 10:05:58 +0000206 /**
207 * Reads the flag value from DeviceConfig to determine whether app compaction
208 * should be enabled, and starts/stops the compaction thread as needed.
209 */
210 @GuardedBy("mPhenotypeFlagLock")
211 private void updateUseCompaction() {
212 String useCompactionFlag =
213 DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
214 KEY_USE_COMPACTION);
215 mUseCompaction = TextUtils.isEmpty(useCompactionFlag)
216 ? DEFAULT_USE_COMPACTION : Boolean.parseBoolean(useCompactionFlag);
217 if (mUseCompaction && !mCompactionThread.isAlive()) {
218 mCompactionThread.start();
219 mCompactionHandler = new MemCompactionHandler();
220 }
221 }
222
223 @GuardedBy("mPhenotypeFlagLock")
224 private void updateCompactionActions() {
225 String compactAction1Flag =
226 DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
227 KEY_COMPACT_ACTION_1);
228 String compactAction2Flag =
229 DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
230 KEY_COMPACT_ACTION_2);
231
232 int compactAction1 = DEFAULT_COMPACT_ACTION_1;
233 try {
234 compactAction1 = TextUtils.isEmpty(compactAction1Flag)
235 ? DEFAULT_COMPACT_ACTION_1 : Integer.parseInt(compactAction1Flag);
236 } catch (NumberFormatException e) {
237 // Do nothing, leave default.
238 }
239
240 int compactAction2 = DEFAULT_COMPACT_ACTION_2;
241 try {
242 compactAction2 = TextUtils.isEmpty(compactAction2Flag)
243 ? DEFAULT_COMPACT_ACTION_2 : Integer.parseInt(compactAction2Flag);
244 } catch (NumberFormatException e) {
245 // Do nothing, leave default.
246 }
247
248 mCompactActionSome = compactActionIntToString(compactAction1);
249 mCompactActionFull = compactActionIntToString(compactAction2);
250 }
251
252 @GuardedBy("mPhenotypeFlagLock")
253 private void updateCompactionThrottles() {
254 boolean useThrottleDefaults = false;
255 String throttleSomeSomeFlag =
256 DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
257 KEY_COMPACT_THROTTLE_1);
258 String throttleSomeFullFlag =
259 DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
260 KEY_COMPACT_THROTTLE_2);
261 String throttleFullSomeFlag =
262 DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
263 KEY_COMPACT_THROTTLE_3);
264 String throttleFullFullFlag =
265 DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
266 KEY_COMPACT_THROTTLE_4);
267
268 if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
269 || TextUtils.isEmpty(throttleFullSomeFlag)
270 || TextUtils.isEmpty(throttleFullFullFlag)) {
271 // Set defaults for all if any are not set.
272 useThrottleDefaults = true;
273 } else {
274 try {
275 mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag);
276 mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
277 mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
278 mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
279 } catch (NumberFormatException e) {
280 useThrottleDefaults = true;
281 }
282 }
283
284 if (useThrottleDefaults) {
285 mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
286 mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
287 mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
288 mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
289 }
290 }
291
292 @VisibleForTesting
293 static String compactActionIntToString(int action) {
294 switch(action) {
295 case COMPACT_ACTION_FILE_FLAG:
296 return COMPACT_ACTION_FILE;
297 case COMPACT_ACTION_ANON_FLAG:
298 return COMPACT_ACTION_ANON;
299 case COMPACT_ACTION_FULL_FLAG:
300 return COMPACT_ACTION_FULL;
301 default:
302 return COMPACT_ACTION_FILE;
303 }
304 }
305
306 private final class MemCompactionHandler extends Handler {
307 private MemCompactionHandler() {
308 super(mCompactionThread.getLooper());
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800309 }
310
311 @Override
312 public void handleMessage(Message msg) {
313 switch (msg.what) {
Ben Murdochc26a5a82019-01-16 10:05:58 +0000314 case COMPACT_PROCESS_MSG: {
315 long start = SystemClock.uptimeMillis();
316 ProcessRecord proc;
317 int pid;
318 String action;
319 final String name;
320 int pendingAction, lastCompactAction;
321 long lastCompactTime;
322 synchronized (mAm) {
323 proc = mPendingCompactionProcesses.remove(0);
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800324
Ben Murdochc26a5a82019-01-16 10:05:58 +0000325 // don't compact if the process has returned to perceptible
326 if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
327 return;
328 }
329
330 pid = proc.pid;
331 name = proc.processName;
332 pendingAction = proc.reqCompactAction;
333 lastCompactAction = proc.lastCompactAction;
334 lastCompactTime = proc.lastCompactTime;
335 }
336
337 if (pid == 0) {
338 // not a real process, either one being launched or one being killed
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800339 return;
340 }
341
Ben Murdochc26a5a82019-01-16 10:05:58 +0000342 // basic throttling
343 // use the Phenotype flag knobs to determine whether current/prevous
344 // compaction combo should be throtted or not
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800345
Ben Murdochc26a5a82019-01-16 10:05:58 +0000346 // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
347 // should very seldom change, and taking the risk of using the wrong action is
348 // preferable to taking the lock for every single compaction action.
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800349 if (pendingAction == COMPACT_PROCESS_SOME) {
Ben Murdochc26a5a82019-01-16 10:05:58 +0000350 if ((lastCompactAction == COMPACT_PROCESS_SOME
351 && (start - lastCompactTime < mCompactThrottleSomeSome))
352 || (lastCompactAction == COMPACT_PROCESS_FULL
353 && (start - lastCompactTime
354 < mCompactThrottleSomeFull))) {
355 return;
356 }
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800357 } else {
Ben Murdochc26a5a82019-01-16 10:05:58 +0000358 if ((lastCompactAction == COMPACT_PROCESS_SOME
359 && (start - lastCompactTime < mCompactThrottleFullSome))
360 || (lastCompactAction == COMPACT_PROCESS_FULL
361 && (start - lastCompactTime
362 < mCompactThrottleFullFull))) {
363 return;
364 }
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800365 }
Ben Murdochc26a5a82019-01-16 10:05:58 +0000366
367 if (pendingAction == COMPACT_PROCESS_SOME) {
368 action = mCompactActionSome;
369 } else {
370 action = mCompactActionFull;
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800371 }
Ben Murdochc26a5a82019-01-16 10:05:58 +0000372
373 try {
374 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
375 + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
376 + ": " + name);
377 long[] rssBefore = Process.getRss(pid);
378 FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
379 fos.write(action.getBytes());
380 fos.close();
381 long[] rssAfter = Process.getRss(pid);
382 long end = SystemClock.uptimeMillis();
383 long time = end - start;
384 EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
385 rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
386 rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
387 lastCompactAction, lastCompactTime, msg.arg1, msg.arg2);
388 StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
389 rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
390 rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
391 lastCompactAction, lastCompactTime, msg.arg1,
392 ActivityManager.processStateAmToProto(msg.arg2));
393 synchronized (mAm) {
394 proc.lastCompactTime = end;
395 proc.lastCompactAction = pendingAction;
396 }
397 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
398 } catch (Exception e) {
399 // nothing to do, presumably the process died
400 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
401 }
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800402 }
403 }
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800404 }
405 }
Tim Murray1cdbd1e2018-12-18 15:18:33 -0800406}