/*
|
* Copyright (C) 2018 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.server.am;
|
|
import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
|
|
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
|
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
|
|
import android.app.ActivityManager;
|
import android.app.ActivityThread;
|
import android.os.Debug;
|
import android.os.Handler;
|
import android.os.Message;
|
import android.os.Process;
|
import android.os.SystemClock;
|
import android.os.Trace;
|
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig.OnPropertiesChangedListener;
|
import android.provider.DeviceConfig.Properties;
|
import android.text.TextUtils;
|
import android.util.EventLog;
|
import android.util.Slog;
|
import android.util.StatsLog;
|
|
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.server.ServiceThread;
|
|
import java.io.FileOutputStream;
|
import java.io.PrintWriter;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.HashSet;
|
import java.util.LinkedHashMap;
|
import java.util.Map;
|
import java.util.Random;
|
import java.util.Set;
|
|
public final class AppCompactor {
|
|
// Flags stored in the DeviceConfig API.
|
@VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
|
@VisibleForTesting static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
|
@VisibleForTesting static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
|
@VisibleForTesting static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
|
@VisibleForTesting static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
|
@VisibleForTesting static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
|
@VisibleForTesting static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
|
@VisibleForTesting static final String KEY_COMPACT_THROTTLE_5 = "compact_throttle_5";
|
@VisibleForTesting static final String KEY_COMPACT_THROTTLE_6 = "compact_throttle_6";
|
@VisibleForTesting static final String KEY_COMPACT_STATSD_SAMPLE_RATE =
|
"compact_statsd_sample_rate";
|
@VisibleForTesting static final String KEY_COMPACT_FULL_RSS_THROTTLE_KB =
|
"compact_full_rss_throttle_kb";
|
@VisibleForTesting static final String KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB =
|
"compact_full_delta_rss_throttle_kb";
|
@VisibleForTesting static final String KEY_COMPACT_PROC_STATE_THROTTLE =
|
"compact_proc_state_throttle";
|
|
// Phenotype sends int configurations and we map them to the strings we'll use on device,
|
// preventing a weird string value entering the kernel.
|
private static final int COMPACT_ACTION_FILE_FLAG = 1;
|
private static final int COMPACT_ACTION_ANON_FLAG = 2;
|
private static final int COMPACT_ACTION_FULL_FLAG = 3;
|
private static final int COMPACT_ACTION_NONE_FLAG = 4;
|
private static final String COMPACT_ACTION_NONE = "";
|
private static final String COMPACT_ACTION_FILE = "file";
|
private static final String COMPACT_ACTION_ANON = "anon";
|
private static final String COMPACT_ACTION_FULL = "all";
|
|
// Defaults for phenotype flags.
|
@VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
|
@VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
|
@VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;
|
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
|
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
|
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
|
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000;
|
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_5 = 10 * 60 * 1000;
|
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000;
|
// The sampling rate to push app compaction events into statsd for upload.
|
@VisibleForTesting static final float DEFAULT_STATSD_SAMPLE_RATE = 0.1f;
|
@VisibleForTesting static final long DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB = 12_000L;
|
@VisibleForTesting static final long DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB = 8_000L;
|
// Format of this string should be a comma separated list of integers.
|
@VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
|
String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
|
|
@VisibleForTesting
|
interface PropertyChangedCallbackForTest {
|
void onPropertyChanged();
|
}
|
private PropertyChangedCallbackForTest mTestCallback;
|
|
// Handler constants.
|
static final int COMPACT_PROCESS_SOME = 1;
|
static final int COMPACT_PROCESS_FULL = 2;
|
static final int COMPACT_PROCESS_PERSISTENT = 3;
|
static final int COMPACT_PROCESS_BFGS = 4;
|
static final int COMPACT_PROCESS_MSG = 1;
|
static final int COMPACT_SYSTEM_MSG = 2;
|
|
/**
|
* This thread must be moved to the system background cpuset.
|
* If that doesn't happen, it's probably going to draw a lot of power.
|
* However, this has to happen after the first updateOomAdjLocked, because
|
* that will wipe out the cpuset assignment for system_server threads.
|
* Accordingly, this is in the AMS constructor.
|
*/
|
final ServiceThread mCompactionThread;
|
|
private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
|
new ArrayList<ProcessRecord>();
|
private final ActivityManagerService mAm;
|
private final OnPropertiesChangedListener mOnFlagsChangedListener =
|
new OnPropertiesChangedListener() {
|
@Override
|
public void onPropertiesChanged(Properties properties) {
|
synchronized (mPhenotypeFlagLock) {
|
for (String name : properties.getKeyset()) {
|
if (KEY_USE_COMPACTION.equals(name)) {
|
updateUseCompaction();
|
} else if (KEY_COMPACT_ACTION_1.equals(name)
|
|| KEY_COMPACT_ACTION_2.equals(name)) {
|
updateCompactionActions();
|
} else if (KEY_COMPACT_THROTTLE_1.equals(name)
|
|| KEY_COMPACT_THROTTLE_2.equals(name)
|
|| KEY_COMPACT_THROTTLE_3.equals(name)
|
|| KEY_COMPACT_THROTTLE_4.equals(name)) {
|
updateCompactionThrottles();
|
} else if (KEY_COMPACT_STATSD_SAMPLE_RATE.equals(name)) {
|
updateStatsdSampleRate();
|
} else if (KEY_COMPACT_FULL_RSS_THROTTLE_KB.equals(name)) {
|
updateFullRssThrottle();
|
} else if (KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB.equals(name)) {
|
updateFullDeltaRssThrottle();
|
} else if (KEY_COMPACT_PROC_STATE_THROTTLE.equals(name)) {
|
updateProcStateThrottle();
|
}
|
}
|
}
|
if (mTestCallback != null) {
|
mTestCallback.onPropertyChanged();
|
}
|
}
|
};
|
|
private final Object mPhenotypeFlagLock = new Object();
|
|
// Configured by phenotype. Updates from the server take effect immediately.
|
@GuardedBy("mPhenotypeFlagLock")
|
@VisibleForTesting volatile String mCompactActionSome =
|
compactActionIntToString(DEFAULT_COMPACT_ACTION_1);
|
@GuardedBy("mPhenotypeFlagLock")
|
@VisibleForTesting volatile String mCompactActionFull =
|
compactActionIntToString(DEFAULT_COMPACT_ACTION_2);
|
@GuardedBy("mPhenotypeFlagLock")
|
@VisibleForTesting volatile long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
|
@GuardedBy("mPhenotypeFlagLock")
|
@VisibleForTesting volatile long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
|
@GuardedBy("mPhenotypeFlagLock")
|
@VisibleForTesting volatile long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
|
@GuardedBy("mPhenotypeFlagLock")
|
@VisibleForTesting volatile long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
|
@GuardedBy("mPhenotypeFlagLock")
|
@VisibleForTesting volatile long mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
|
@GuardedBy("mPhenotypeFlagLock")
|
@VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
|
@GuardedBy("mPhenotypeFlagLock")
|
private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION;
|
private final Random mRandom = new Random();
|
@GuardedBy("mPhenotypeFlagLock")
|
@VisibleForTesting volatile float mStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE;
|
@GuardedBy("mPhenotypeFlagLock")
|
@VisibleForTesting volatile long mFullAnonRssThrottleKb =
|
DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
|
@GuardedBy("mPhenoypeFlagLock")
|
@VisibleForTesting volatile long mFullDeltaRssThrottleKb =
|
DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
|
@GuardedBy("mPhenoypeFlagLock")
|
@VisibleForTesting final Set<Integer> mProcStateThrottle;
|
|
// Handler on which compaction runs.
|
private Handler mCompactionHandler;
|
|
// Maps process ID to last compaction statistics for processes that we've fully compacted. Used
|
// when evaluating throttles that we only consider for "full" compaction, so we don't store
|
// data for "some" compactions.
|
private Map<Integer, LastCompactionStats> mLastCompactionStats =
|
new LinkedHashMap<Integer, LastCompactionStats>() {
|
@Override
|
protected boolean removeEldestEntry(Map.Entry eldest) {
|
return size() > 100;
|
}
|
};
|
|
private int mSomeCompactionCount;
|
private int mFullCompactionCount;
|
private int mPersistentCompactionCount;
|
private int mBfgsCompactionCount;
|
|
public AppCompactor(ActivityManagerService am) {
|
mAm = am;
|
mCompactionThread = new ServiceThread("CompactionThread",
|
THREAD_PRIORITY_FOREGROUND, true);
|
mProcStateThrottle = new HashSet<>();
|
}
|
|
@VisibleForTesting
|
AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
|
this(am);
|
mTestCallback = callback;
|
}
|
|
/**
|
* Reads phenotype config to determine whether app compaction is enabled or not and
|
* starts the background thread if necessary.
|
*/
|
public void init() {
|
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
|
synchronized (mPhenotypeFlagLock) {
|
updateUseCompaction();
|
updateCompactionActions();
|
updateCompactionThrottles();
|
updateStatsdSampleRate();
|
updateFullRssThrottle();
|
updateFullDeltaRssThrottle();
|
updateProcStateThrottle();
|
}
|
Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
|
Process.THREAD_GROUP_SYSTEM);
|
}
|
|
/**
|
* Returns whether compaction is enabled.
|
*/
|
public boolean useCompaction() {
|
synchronized (mPhenotypeFlagLock) {
|
return mUseCompaction;
|
}
|
}
|
|
@GuardedBy("mAm")
|
void dump(PrintWriter pw) {
|
pw.println("AppCompactor settings");
|
synchronized (mPhenotypeFlagLock) {
|
pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction);
|
pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
|
pw.println(" " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull);
|
pw.println(" " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome);
|
pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
|
pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
|
pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
|
pw.println(" " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS);
|
pw.println(" " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent);
|
pw.println(" " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mStatsdSampleRate);
|
pw.println(" " + KEY_COMPACT_FULL_RSS_THROTTLE_KB + "="
|
+ mFullAnonRssThrottleKb);
|
pw.println(" " + KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + "="
|
+ mFullDeltaRssThrottleKb);
|
pw.println(" " + KEY_COMPACT_PROC_STATE_THROTTLE + "="
|
+ Arrays.toString(mProcStateThrottle.toArray(new Integer[0])));
|
|
pw.println(" " + mSomeCompactionCount + " some, " + mFullCompactionCount
|
+ " full, " + mPersistentCompactionCount + " persistent, "
|
+ mBfgsCompactionCount + " BFGS compactions.");
|
|
pw.println(" Tracking last compaction stats for " + mLastCompactionStats.size()
|
+ " processes.");
|
if (DEBUG_COMPACTION) {
|
for (Map.Entry<Integer, LastCompactionStats> entry
|
: mLastCompactionStats.entrySet()) {
|
int pid = entry.getKey();
|
LastCompactionStats stats = entry.getValue();
|
pw.println(" " + pid + ": "
|
+ Arrays.toString(stats.getRssAfterCompaction()));
|
}
|
}
|
}
|
}
|
|
@GuardedBy("mAm")
|
void compactAppSome(ProcessRecord app) {
|
app.reqCompactAction = COMPACT_PROCESS_SOME;
|
mPendingCompactionProcesses.add(app);
|
mCompactionHandler.sendMessage(
|
mCompactionHandler.obtainMessage(
|
COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
|
}
|
|
@GuardedBy("mAm")
|
void compactAppFull(ProcessRecord app) {
|
app.reqCompactAction = COMPACT_PROCESS_FULL;
|
mPendingCompactionProcesses.add(app);
|
mCompactionHandler.sendMessage(
|
mCompactionHandler.obtainMessage(
|
COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
|
|
}
|
|
@GuardedBy("mAm")
|
void compactAppPersistent(ProcessRecord app) {
|
app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
|
mPendingCompactionProcesses.add(app);
|
mCompactionHandler.sendMessage(
|
mCompactionHandler.obtainMessage(
|
COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
|
}
|
|
@GuardedBy("mAm")
|
boolean shouldCompactPersistent(ProcessRecord app, long now) {
|
return (app.lastCompactTime == 0
|
|| (now - app.lastCompactTime) > mCompactThrottlePersistent);
|
}
|
|
@GuardedBy("mAm")
|
void compactAppBfgs(ProcessRecord app) {
|
app.reqCompactAction = COMPACT_PROCESS_BFGS;
|
mPendingCompactionProcesses.add(app);
|
mCompactionHandler.sendMessage(
|
mCompactionHandler.obtainMessage(
|
COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
|
}
|
|
@GuardedBy("mAm")
|
boolean shouldCompactBFGS(ProcessRecord app, long now) {
|
return (app.lastCompactTime == 0
|
|| (now - app.lastCompactTime) > mCompactThrottleBFGS);
|
}
|
|
@GuardedBy("mAm")
|
void compactAllSystem() {
|
if (mUseCompaction) {
|
mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
|
COMPACT_SYSTEM_MSG));
|
}
|
}
|
|
private native void compactSystem();
|
|
/**
|
* Reads the flag value from DeviceConfig to determine whether app compaction
|
* should be enabled, and starts the compaction thread if needed.
|
*/
|
@GuardedBy("mPhenotypeFlagLock")
|
private void updateUseCompaction() {
|
mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
|
if (mUseCompaction && !mCompactionThread.isAlive()) {
|
mCompactionThread.start();
|
mCompactionHandler = new MemCompactionHandler();
|
}
|
}
|
|
@GuardedBy("mPhenotypeFlagLock")
|
private void updateCompactionActions() {
|
int compactAction1 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
|
|
int compactAction2 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
|
|
mCompactActionSome = compactActionIntToString(compactAction1);
|
mCompactActionFull = compactActionIntToString(compactAction2);
|
}
|
|
@GuardedBy("mPhenotypeFlagLock")
|
private void updateCompactionThrottles() {
|
boolean useThrottleDefaults = false;
|
String throttleSomeSomeFlag =
|
DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_THROTTLE_1);
|
String throttleSomeFullFlag =
|
DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_THROTTLE_2);
|
String throttleFullSomeFlag =
|
DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_THROTTLE_3);
|
String throttleFullFullFlag =
|
DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_THROTTLE_4);
|
String throttleBFGSFlag =
|
DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_THROTTLE_5);
|
String throttlePersistentFlag =
|
DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_THROTTLE_6);
|
|
if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
|
|| TextUtils.isEmpty(throttleFullSomeFlag)
|
|| TextUtils.isEmpty(throttleFullFullFlag)
|
|| TextUtils.isEmpty(throttleBFGSFlag)
|
|| TextUtils.isEmpty(throttlePersistentFlag)) {
|
// Set defaults for all if any are not set.
|
useThrottleDefaults = true;
|
} else {
|
try {
|
mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag);
|
mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
|
mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
|
mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
|
mCompactThrottleBFGS = Integer.parseInt(throttleBFGSFlag);
|
mCompactThrottlePersistent = Integer.parseInt(throttlePersistentFlag);
|
} catch (NumberFormatException e) {
|
useThrottleDefaults = true;
|
}
|
}
|
|
if (useThrottleDefaults) {
|
mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
|
mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
|
mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
|
mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
|
mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
|
mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
|
}
|
}
|
|
@GuardedBy("mPhenotypeFlagLock")
|
private void updateStatsdSampleRate() {
|
mStatsdSampleRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_STATSD_SAMPLE_RATE, DEFAULT_STATSD_SAMPLE_RATE);
|
mStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mStatsdSampleRate));
|
}
|
|
@GuardedBy("mPhenotypeFlagLock")
|
private void updateFullRssThrottle() {
|
mFullAnonRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_FULL_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
|
|
// Don't allow negative values. 0 means don't apply the throttle.
|
if (mFullAnonRssThrottleKb < 0) {
|
mFullAnonRssThrottleKb = DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
|
}
|
}
|
|
@GuardedBy("mPhenotypeFlagLock")
|
private void updateFullDeltaRssThrottle() {
|
mFullDeltaRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
|
|
if (mFullDeltaRssThrottleKb < 0) {
|
mFullDeltaRssThrottleKb = DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
|
}
|
}
|
|
@GuardedBy("mPhenotypeFlagLock")
|
private void updateProcStateThrottle() {
|
String procStateThrottleString = DeviceConfig.getString(
|
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_PROC_STATE_THROTTLE,
|
DEFAULT_COMPACT_PROC_STATE_THROTTLE);
|
if (!parseProcStateThrottle(procStateThrottleString)) {
|
Slog.w(TAG_AM, "Unable to parse app compact proc state throttle \""
|
+ procStateThrottleString + "\" falling back to default.");
|
if (!parseProcStateThrottle(DEFAULT_COMPACT_PROC_STATE_THROTTLE)) {
|
Slog.wtf(TAG_AM,
|
"Unable to parse default app compact proc state throttle "
|
+ DEFAULT_COMPACT_PROC_STATE_THROTTLE);
|
}
|
}
|
}
|
|
private boolean parseProcStateThrottle(String procStateThrottleString) {
|
String[] procStates = TextUtils.split(procStateThrottleString, ",");
|
mProcStateThrottle.clear();
|
for (String procState : procStates) {
|
try {
|
mProcStateThrottle.add(Integer.parseInt(procState));
|
} catch (NumberFormatException e) {
|
Slog.e(TAG_AM, "Failed to parse default app compaction proc state: "
|
+ procState);
|
return false;
|
}
|
}
|
return true;
|
}
|
|
@VisibleForTesting
|
static String compactActionIntToString(int action) {
|
switch(action) {
|
case COMPACT_ACTION_NONE_FLAG:
|
return COMPACT_ACTION_NONE;
|
case COMPACT_ACTION_FILE_FLAG:
|
return COMPACT_ACTION_FILE;
|
case COMPACT_ACTION_ANON_FLAG:
|
return COMPACT_ACTION_ANON;
|
case COMPACT_ACTION_FULL_FLAG:
|
return COMPACT_ACTION_FULL;
|
default:
|
return COMPACT_ACTION_NONE;
|
}
|
}
|
|
private static final class LastCompactionStats {
|
private final long[] mRssAfterCompaction;
|
|
LastCompactionStats(long[] rss) {
|
mRssAfterCompaction = rss;
|
}
|
|
long[] getRssAfterCompaction() {
|
return mRssAfterCompaction;
|
}
|
}
|
|
private final class MemCompactionHandler extends Handler {
|
private MemCompactionHandler() {
|
super(mCompactionThread.getLooper());
|
}
|
|
@Override
|
public void handleMessage(Message msg) {
|
switch (msg.what) {
|
case COMPACT_PROCESS_MSG: {
|
long start = SystemClock.uptimeMillis();
|
ProcessRecord proc;
|
int pid;
|
String action;
|
final String name;
|
int pendingAction, lastCompactAction;
|
long lastCompactTime;
|
LastCompactionStats lastCompactionStats;
|
int lastOomAdj = msg.arg1;
|
int procState = msg.arg2;
|
synchronized (mAm) {
|
proc = mPendingCompactionProcesses.remove(0);
|
|
pendingAction = proc.reqCompactAction;
|
pid = proc.pid;
|
name = proc.processName;
|
|
// don't compact if the process has returned to perceptible
|
// and this is only a cached/home/prev compaction
|
if ((pendingAction == COMPACT_PROCESS_SOME
|
|| pendingAction == COMPACT_PROCESS_FULL)
|
&& (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
|
if (DEBUG_COMPACTION) {
|
Slog.d(TAG_AM,
|
"Skipping compaction as process " + name + " is "
|
+ "now perceptible.");
|
}
|
return;
|
}
|
|
lastCompactAction = proc.lastCompactAction;
|
lastCompactTime = proc.lastCompactTime;
|
// remove rather than get so that insertion order will be updated when we
|
// put the post-compaction stats back into the map.
|
lastCompactionStats = mLastCompactionStats.remove(pid);
|
}
|
|
if (pid == 0) {
|
// not a real process, either one being launched or one being killed
|
return;
|
}
|
|
// basic throttling
|
// use the Phenotype flag knobs to determine whether current/prevous
|
// compaction combo should be throtted or not
|
|
// Note that we explicitly don't take mPhenotypeFlagLock here as the flags
|
// should very seldom change, and taking the risk of using the wrong action is
|
// preferable to taking the lock for every single compaction action.
|
if (lastCompactTime != 0) {
|
if (pendingAction == COMPACT_PROCESS_SOME) {
|
if ((lastCompactAction == COMPACT_PROCESS_SOME
|
&& (start - lastCompactTime < mCompactThrottleSomeSome))
|
|| (lastCompactAction == COMPACT_PROCESS_FULL
|
&& (start - lastCompactTime
|
< mCompactThrottleSomeFull))) {
|
if (DEBUG_COMPACTION) {
|
Slog.d(TAG_AM, "Skipping some compaction for " + name
|
+ ": too soon. throttle=" + mCompactThrottleSomeSome
|
+ "/" + mCompactThrottleSomeFull + " last="
|
+ (start - lastCompactTime) + "ms ago");
|
}
|
return;
|
}
|
} else if (pendingAction == COMPACT_PROCESS_FULL) {
|
if ((lastCompactAction == COMPACT_PROCESS_SOME
|
&& (start - lastCompactTime < mCompactThrottleFullSome))
|
|| (lastCompactAction == COMPACT_PROCESS_FULL
|
&& (start - lastCompactTime
|
< mCompactThrottleFullFull))) {
|
if (DEBUG_COMPACTION) {
|
Slog.d(TAG_AM, "Skipping full compaction for " + name
|
+ ": too soon. throttle=" + mCompactThrottleFullSome
|
+ "/" + mCompactThrottleFullFull + " last="
|
+ (start - lastCompactTime) + "ms ago");
|
}
|
return;
|
}
|
} else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
|
if (start - lastCompactTime < mCompactThrottlePersistent) {
|
if (DEBUG_COMPACTION) {
|
Slog.d(TAG_AM, "Skipping persistent compaction for " + name
|
+ ": too soon. throttle=" + mCompactThrottlePersistent
|
+ " last=" + (start - lastCompactTime) + "ms ago");
|
}
|
return;
|
}
|
} else if (pendingAction == COMPACT_PROCESS_BFGS) {
|
if (start - lastCompactTime < mCompactThrottleBFGS) {
|
if (DEBUG_COMPACTION) {
|
Slog.d(TAG_AM, "Skipping bfgs compaction for " + name
|
+ ": too soon. throttle=" + mCompactThrottleBFGS
|
+ " last=" + (start - lastCompactTime) + "ms ago");
|
}
|
return;
|
}
|
}
|
}
|
|
switch (pendingAction) {
|
case COMPACT_PROCESS_SOME:
|
action = mCompactActionSome;
|
break;
|
// For the time being, treat these as equivalent.
|
case COMPACT_PROCESS_FULL:
|
case COMPACT_PROCESS_PERSISTENT:
|
case COMPACT_PROCESS_BFGS:
|
action = mCompactActionFull;
|
break;
|
default:
|
action = COMPACT_ACTION_NONE;
|
break;
|
}
|
|
if (COMPACT_ACTION_NONE.equals(action)) {
|
return;
|
}
|
|
if (mProcStateThrottle.contains(procState)) {
|
if (DEBUG_COMPACTION) {
|
Slog.d(TAG_AM, "Skipping full compaction for process " + name
|
+ "; proc state is " + procState);
|
}
|
return;
|
}
|
|
long[] rssBefore = Process.getRss(pid);
|
long anonRssBefore = rssBefore[2];
|
|
if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
|
&& rssBefore[3] == 0) {
|
if (DEBUG_COMPACTION) {
|
Slog.d(TAG_AM, "Skipping compaction for" + "process " + pid
|
+ " with no memory usage. Dead?");
|
}
|
return;
|
}
|
|
if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) {
|
if (mFullAnonRssThrottleKb > 0L
|
&& anonRssBefore < mFullAnonRssThrottleKb) {
|
if (DEBUG_COMPACTION) {
|
Slog.d(TAG_AM, "Skipping full compaction for process "
|
+ name + "; anon RSS is too small: " + anonRssBefore
|
+ "KB.");
|
}
|
return;
|
}
|
|
if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
|
long[] lastRss = lastCompactionStats.getRssAfterCompaction();
|
long absDelta = Math.abs(rssBefore[1] - lastRss[1])
|
+ Math.abs(rssBefore[2] - lastRss[2])
|
+ Math.abs(rssBefore[3] - lastRss[3]);
|
if (absDelta <= mFullDeltaRssThrottleKb) {
|
if (DEBUG_COMPACTION) {
|
Slog.d(TAG_AM, "Skipping full compaction for process "
|
+ name + "; abs delta is too small: " + absDelta
|
+ "KB.");
|
}
|
return;
|
}
|
}
|
}
|
|
// Now we've passed through all the throttles and are going to compact, update
|
// bookkeeping.
|
switch (pendingAction) {
|
case COMPACT_PROCESS_SOME:
|
mSomeCompactionCount++;
|
break;
|
case COMPACT_PROCESS_FULL:
|
mFullCompactionCount++;
|
break;
|
case COMPACT_PROCESS_PERSISTENT:
|
mPersistentCompactionCount++;
|
break;
|
case COMPACT_PROCESS_BFGS:
|
mBfgsCompactionCount++;
|
break;
|
default:
|
break;
|
}
|
|
try {
|
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
|
+ ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
|
+ ": " + name);
|
long zramFreeKbBefore = Debug.getZramFreeKb();
|
FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
|
fos.write(action.getBytes());
|
fos.close();
|
long[] rssAfter = Process.getRss(pid);
|
long end = SystemClock.uptimeMillis();
|
long time = end - start;
|
long zramFreeKbAfter = Debug.getZramFreeKb();
|
EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
|
rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
|
rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1],
|
rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
|
lastCompactAction, lastCompactTime, lastOomAdj, procState,
|
zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
|
|
// Note that as above not taking mPhenoTypeFlagLock here to avoid locking
|
// on every single compaction for a flag that will seldom change and the
|
// impact of reading the wrong value here is low.
|
if (mRandom.nextFloat() < mStatsdSampleRate) {
|
StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
|
rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
|
rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
|
lastCompactAction, lastCompactTime, lastOomAdj,
|
ActivityManager.processStateAmToProto(procState),
|
zramFreeKbBefore, zramFreeKbAfter);
|
}
|
|
synchronized (mAm) {
|
proc.lastCompactTime = end;
|
proc.lastCompactAction = pendingAction;
|
}
|
|
if (action.equals(COMPACT_ACTION_FULL)
|
|| action.equals(COMPACT_ACTION_ANON)) {
|
mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
|
}
|
} catch (Exception e) {
|
// nothing to do, presumably the process died
|
} finally {
|
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
}
|
break;
|
}
|
case COMPACT_SYSTEM_MSG: {
|
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
|
compactSystem();
|
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
break;
|
}
|
}
|
}
|
}
|
}
|