blob: 626b70b51093e4298e37647eaa7fa640f0471748 [file] [log] [blame]
/*
* 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.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FREEZER;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import android.annotation.IntDef;
import android.annotation.UptimeMillisLong;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
import android.app.ApplicationExitInfo.Reason;
import android.app.ApplicationExitInfo.SubReason;
import android.app.IApplicationThread;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BinderfsStatsReader;
import com.android.internal.os.ProcLocksReader;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ServiceThread;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Random;
import java.util.Set;
public final class CachedAppOptimizer {
// Flags stored in the DeviceConfig API.
@VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
@VisibleForTesting static final String KEY_USE_FREEZER = "use_freezer";
@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_THROTTLE_MIN_OOM_ADJ =
"compact_throttle_min_oom_adj";
@VisibleForTesting static final String KEY_COMPACT_THROTTLE_MAX_OOM_ADJ =
"compact_throttle_max_oom_adj";
@VisibleForTesting static final String KEY_COMPACT_STATSD_SAMPLE_RATE =
"compact_statsd_sample_rate";
@VisibleForTesting static final String KEY_FREEZER_STATSD_SAMPLE_RATE =
"freeze_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";
@VisibleForTesting static final String KEY_FREEZER_DEBOUNCE_TIMEOUT =
"freeze_debounce_timeout";
@VisibleForTesting static final String KEY_FREEZER_EXEMPT_INST_PKG =
"freeze_exempt_inst_pkg";
@VisibleForTesting static final String KEY_FREEZER_BINDER_ENABLED =
"freeze_binder_enabled";
@VisibleForTesting static final String KEY_FREEZER_BINDER_DIVISOR =
"freeze_binder_divisor";
@VisibleForTesting static final String KEY_FREEZER_BINDER_OFFSET =
"freeze_binder_offset";
@VisibleForTesting static final String KEY_FREEZER_BINDER_THRESHOLD =
"freeze_binder_threshold";
@VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_ENABLED =
"freeze_binder_callback_enabled";
@VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_THROTTLE =
"freeze_binder_callback_throttle";
@VisibleForTesting static final String KEY_FREEZER_BINDER_ASYNC_THRESHOLD =
"freeze_binder_async_threshold";
static final int UNFREEZE_REASON_NONE =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_NONE;
static final int UNFREEZE_REASON_ACTIVITY =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_ACTIVITY;
static final int UNFREEZE_REASON_FINISH_RECEIVER =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FINISH_RECEIVER;
static final int UNFREEZE_REASON_START_RECEIVER =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_START_RECEIVER;
static final int UNFREEZE_REASON_BIND_SERVICE =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BIND_SERVICE;
static final int UNFREEZE_REASON_UNBIND_SERVICE =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UNBIND_SERVICE;
static final int UNFREEZE_REASON_START_SERVICE =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_START_SERVICE;
static final int UNFREEZE_REASON_GET_PROVIDER =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_GET_PROVIDER;
static final int UNFREEZE_REASON_REMOVE_PROVIDER =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_PROVIDER;
static final int UNFREEZE_REASON_UI_VISIBILITY =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UI_VISIBILITY;
static final int UNFREEZE_REASON_ALLOWLIST =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_ALLOWLIST;
static final int UNFREEZE_REASON_PROCESS_BEGIN =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_PROCESS_BEGIN;
static final int UNFREEZE_REASON_PROCESS_END =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_PROCESS_END;
static final int UNFREEZE_REASON_TRIM_MEMORY =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_TRIM_MEMORY;
static final int UNFREEZE_REASON_PING =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_PING;
static final int UNFREEZE_REASON_FILE_LOCKS =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FILE_LOCKS;
static final int UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FILE_LOCK_CHECK_FAILURE;
static final int UNFREEZE_REASON_BINDER_TXNS =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS;
static final int UNFREEZE_REASON_FEATURE_FLAGS =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS;
static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT;
static final int UNFREEZE_REASON_SYSTEM_INIT =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT;
static final int UNFREEZE_REASON_BACKUP =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP;
static final int UNFREEZE_REASON_SHELL =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL;
static final int UNFREEZE_REASON_REMOVE_TASK =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK;
static final int UNFREEZE_REASON_UID_IDLE =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE;
static final int UNFREEZE_REASON_STOP_SERVICE =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE;
static final int UNFREEZE_REASON_EXECUTING_SERVICE =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE;
static final int UNFREEZE_REASON_RESTRICTION_CHANGE =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
static final int UNFREEZE_REASON_COMPONENT_DISABLED =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
@IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
UNFREEZE_REASON_NONE,
UNFREEZE_REASON_ACTIVITY,
UNFREEZE_REASON_FINISH_RECEIVER,
UNFREEZE_REASON_START_RECEIVER,
UNFREEZE_REASON_BIND_SERVICE,
UNFREEZE_REASON_UNBIND_SERVICE,
UNFREEZE_REASON_START_SERVICE,
UNFREEZE_REASON_GET_PROVIDER,
UNFREEZE_REASON_REMOVE_PROVIDER,
UNFREEZE_REASON_UI_VISIBILITY,
UNFREEZE_REASON_ALLOWLIST,
UNFREEZE_REASON_PROCESS_BEGIN,
UNFREEZE_REASON_PROCESS_END,
UNFREEZE_REASON_TRIM_MEMORY,
UNFREEZE_REASON_PING,
UNFREEZE_REASON_FILE_LOCKS,
UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE,
UNFREEZE_REASON_BINDER_TXNS,
UNFREEZE_REASON_FEATURE_FLAGS,
UNFREEZE_REASON_SHORT_FGS_TIMEOUT,
UNFREEZE_REASON_SYSTEM_INIT,
UNFREEZE_REASON_BACKUP,
UNFREEZE_REASON_SHELL,
UNFREEZE_REASON_REMOVE_TASK,
UNFREEZE_REASON_UID_IDLE,
UNFREEZE_REASON_STOP_SERVICE,
UNFREEZE_REASON_EXECUTING_SERVICE,
UNFREEZE_REASON_RESTRICTION_CHANGE,
UNFREEZE_REASON_COMPONENT_DISABLED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UnfreezeReason {}
// RSS Indices
private static final int RSS_TOTAL_INDEX = 0;
private static final int RSS_FILE_INDEX = 1;
private static final int RSS_ANON_INDEX = 2;
private static final int RSS_SWAP_INDEX = 3;
// Keeps these flags in sync with services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
private static final int COMPACT_ACTION_FILE_FLAG = 1;
private static final int COMPACT_ACTION_ANON_FLAG = 2;
private static final String ATRACE_COMPACTION_TRACK = "Compaction";
private static final String ATRACE_FREEZER_TRACK = "Freezer";
private static final int FREEZE_BINDER_TIMEOUT_MS = 0;
private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000;
// If enabled, any compaction issued will apply to code mappings and share memory mappings.
@VisibleForTesting static final boolean ENABLE_SHARED_AND_CODE_COMPACT = false;
// Defaults for phenotype flags.
@VisibleForTesting static final boolean DEFAULT_USE_COMPACTION = true;
@VisibleForTesting static final boolean DEFAULT_USE_FREEZER = true;
@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;
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ =
ProcessList.CACHED_APP_MIN_ADJ;
@VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ =
ProcessList.CACHED_APP_MAX_ADJ;
// 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 static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L;
@VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = false;
@VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_ENABLED = true;
@VisibleForTesting static final long DEFAULT_FREEZER_BINDER_DIVISOR = 4;
@VisibleForTesting static final int DEFAULT_FREEZER_BINDER_OFFSET = 500;
@VisibleForTesting static final long DEFAULT_FREEZER_BINDER_THRESHOLD = 1_000;
@VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED = true;
@VisibleForTesting static final long DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE = 10_000L;
@VisibleForTesting static final int DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD = 1_024;
@VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
Settings.Global.CACHED_APPS_FREEZER_ENABLED);
@VisibleForTesting
interface PropertyChangedCallbackForTest {
void onPropertyChanged();
}
private PropertyChangedCallbackForTest mTestCallback;
// This interface is for functions related to the Process object that need a different
// implementation in the tests as we are not creating real processes when testing compaction.
@VisibleForTesting
interface ProcessDependencies {
long[] getRss(int pid);
void performCompaction(CompactProfile action, int pid) throws IOException;
}
// This indicates the compaction we want to perform
public enum CompactProfile {
NONE, // No compaction
SOME, // File compaction
ANON, // Anon compaction
FULL // File+anon compaction
}
// This indicates who initiated the compaction request
public enum CompactSource { APP, SHELL }
public enum CancelCompactReason {
SCREEN_ON, // screen was turned on which cancels all compactions.
OOM_IMPROVEMENT // process moved out of cached state and into a more perceptible state.
}
// Handler constants.
static final int COMPACT_PROCESS_MSG = 1;
static final int COMPACT_SYSTEM_MSG = 2;
static final int SET_FROZEN_PROCESS_MSG = 3;
static final int REPORT_UNFREEZE_MSG = 4;
static final int COMPACT_NATIVE_MSG = 5;
static final int UID_FROZEN_STATE_CHANGED_MSG = 6;
static final int DEADLOCK_WATCHDOG_MSG = 7;
static final int BINDER_ERROR_MSG = 8;
// When free swap falls below this percentage threshold any full (file + anon)
// compactions will be downgraded to file only compactions to reduce pressure
// on swap resources as file.
static final double COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD = 0.2;
// Size of history for the last 20 compactions for any process
static final int LAST_COMPACTED_ANY_PROCESS_STATS_HISTORY_SIZE = 20;
// Amount of processes supported to record for their last compaction.
static final int LAST_COMPACTION_FOR_PROCESS_STATS_SIZE = 256;
static final int DO_FREEZE = 1;
static final int REPORT_UNFREEZE = 2;
// Bitfield values for sync/async transactions reveived by frozen processes
static final int SYNC_RECEIVED_WHILE_FROZEN = 1;
static final int ASYNC_RECEIVED_WHILE_FROZEN = 2;
// Bitfield values for sync transactions received by frozen binder threads
static final int TXNS_PENDING_WHILE_FROZEN = 4;
/**
* 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 mCachedAppOptimizerThread;
@GuardedBy("mProcLock")
private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
new ArrayList<ProcessRecord>();
@GuardedBy("mProcLock")
private final SparseArray<ProcessRecord> mFrozenProcesses =
new SparseArray<>();
private final ActivityManagerService mAm;
private final ActivityManagerGlobalLock mProcLock;
public final Object mFreezerLock = new Object();
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_THROTTLE_1.equals(name)
|| KEY_COMPACT_THROTTLE_2.equals(name)
|| KEY_COMPACT_THROTTLE_3.equals(name)
|| KEY_COMPACT_THROTTLE_4.equals(name)
|| KEY_COMPACT_THROTTLE_5.equals(name)
|| KEY_COMPACT_THROTTLE_6.equals(name)) {
updateCompactionThrottles();
} else if (KEY_COMPACT_STATSD_SAMPLE_RATE.equals(name)) {
updateCompactStatsdSampleRate();
} else if (KEY_FREEZER_STATSD_SAMPLE_RATE.equals(name)) {
updateFreezerStatsdSampleRate();
} 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();
} else if (KEY_COMPACT_THROTTLE_MIN_OOM_ADJ.equals(name)) {
updateMinOomAdjThrottle();
} else if (KEY_COMPACT_THROTTLE_MAX_OOM_ADJ.equals(name)) {
updateMaxOomAdjThrottle();
}
}
}
if (mTestCallback != null) {
mTestCallback.onPropertyChanged();
}
}
};
private final OnPropertiesChangedListener mOnNativeBootFlagsChangedListener =
new OnPropertiesChangedListener() {
@Override
public void onPropertiesChanged(Properties properties) {
synchronized (mPhenotypeFlagLock) {
for (String name : properties.getKeyset()) {
if (KEY_FREEZER_DEBOUNCE_TIMEOUT.equals(name)) {
updateFreezerDebounceTimeout();
} else if (KEY_FREEZER_EXEMPT_INST_PKG.equals(name)) {
updateFreezerExemptInstPkg();
} else if (KEY_FREEZER_BINDER_ENABLED.equals(name)
|| KEY_FREEZER_BINDER_DIVISOR.equals(name)
|| KEY_FREEZER_BINDER_THRESHOLD.equals(name)
|| KEY_FREEZER_BINDER_OFFSET.equals(name)
|| KEY_FREEZER_BINDER_CALLBACK_ENABLED.equals(name)
|| KEY_FREEZER_BINDER_CALLBACK_THROTTLE.equals(name)
|| KEY_FREEZER_BINDER_ASYNC_THRESHOLD.equals(name)) {
updateFreezerBinderState();
}
}
}
if (mTestCallback != null) {
mTestCallback.onPropertyChanged();
}
}
};
private final class SettingsContentObserver extends ContentObserver {
SettingsContentObserver() {
super(mAm.mHandler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
if (CACHED_APP_FREEZER_ENABLED_URI.equals(uri)) {
synchronized (mPhenotypeFlagLock) {
updateUseFreezer();
}
}
}
}
private final SettingsContentObserver mSettingsObserver;
@VisibleForTesting
final Object mPhenotypeFlagLock = new Object();
// Configured by phenotype. Updates from the server take effect immediately.
@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 mCompactThrottleMinOomAdj =
DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mCompactThrottleMaxOomAdj =
DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ;
@GuardedBy("mPhenotypeFlagLock")
private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION;
private volatile boolean mUseFreezer = false; // set to DEFAULT in init()
@GuardedBy("this")
private int mFreezerDisableCount = 1; // Freezer is initially disabled, until enabled
private final Random mRandom = new Random();
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile float mCompactStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE;
@VisibleForTesting volatile float mFreezerStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mFullAnonRssThrottleKb =
DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mFullDeltaRssThrottleKb =
DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting final Set<Integer> mProcStateThrottle;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile boolean mFreezerBinderEnabled = DEFAULT_FREEZER_BINDER_ENABLED;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mFreezerBinderDivisor = DEFAULT_FREEZER_BINDER_DIVISOR;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile int mFreezerBinderOffset = DEFAULT_FREEZER_BINDER_OFFSET;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mFreezerBinderThreshold = DEFAULT_FREEZER_BINDER_THRESHOLD;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile boolean mFreezerBinderCallbackEnabled =
DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mFreezerBinderCallbackThrottle =
DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE;
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile int mFreezerBinderAsyncThreshold =
DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD;
// Handler on which compaction runs.
@VisibleForTesting
Handler mCompactionHandler;
private Handler mFreezeHandler;
@GuardedBy("mProcLock")
private boolean mFreezerOverride = false;
private long mFreezerBinderCallbackLast = -1;
@VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
@VisibleForTesting volatile boolean mFreezerExemptInstPkg = DEFAULT_FREEZER_EXEMPT_INST_PKG;
// 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. Uses LinkedHashMap to ensure insertion order is kept and
// facilitate removal of the oldest entry.
@VisibleForTesting
@GuardedBy("mProcLock")
LinkedHashMap<Integer, SingleCompactionStats> mLastCompactionStats =
new LinkedHashMap<Integer, SingleCompactionStats>() {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > LAST_COMPACTION_FOR_PROCESS_STATS_SIZE;
}
};
LinkedList<SingleCompactionStats> mCompactionStatsHistory =
new LinkedList<SingleCompactionStats>() {
@Override
public boolean add(SingleCompactionStats e) {
if (size() >= LAST_COMPACTED_ANY_PROCESS_STATS_HISTORY_SIZE) {
this.remove();
}
return super.add(e);
}
};
class AggregatedCompactionStats {
// Throttling stats
public long mFullCompactRequested;
public long mSomeCompactRequested;
public long mFullCompactPerformed;
public long mSomeCompactPerformed;
public long mProcCompactionsNoPidThrottled;
public long mProcCompactionsOomAdjThrottled;
public long mProcCompactionsTimeThrottled;
public long mProcCompactionsRSSThrottled;
public long mProcCompactionsMiscThrottled;
// Memory stats
public long mTotalDeltaAnonRssKBs;
public long mTotalZramConsumedKBs;
public long mTotalAnonMemFreedKBs;
public long mSumOrigAnonRss;
public double mMaxCompactEfficiency;
// Cpu time
public long mTotalCpuTimeMillis;
public long getThrottledSome() { return mSomeCompactRequested - mSomeCompactPerformed; }
public long getThrottledFull() { return mFullCompactRequested - mFullCompactPerformed; }
public void addMemStats(long anonRssSaved, long zramConsumed, long memFreed,
long origAnonRss, long totalCpuTimeMillis) {
final double compactEfficiency = memFreed / (double) origAnonRss;
if (compactEfficiency > mMaxCompactEfficiency) {
mMaxCompactEfficiency = compactEfficiency;
}
mTotalDeltaAnonRssKBs += anonRssSaved;
mTotalZramConsumedKBs += zramConsumed;
mTotalAnonMemFreedKBs += memFreed;
mSumOrigAnonRss += origAnonRss;
mTotalCpuTimeMillis += totalCpuTimeMillis;
}
public void dump(PrintWriter pw) {
long totalCompactRequested = mSomeCompactRequested + mFullCompactRequested;
long totalCompactPerformed = mSomeCompactPerformed + mFullCompactPerformed;
pw.println(" Performed / Requested:");
pw.println(" Some: (" + mSomeCompactPerformed + "/" + mSomeCompactRequested + ")");
pw.println(" Full: (" + mFullCompactPerformed + "/" + mFullCompactRequested + ")");
long throttledSome = getThrottledSome();
long throttledFull = getThrottledFull();
if (throttledSome > 0 || throttledFull > 0) {
pw.println(" Throttled:");
pw.println(" Some: " + throttledSome + " Full: " + throttledFull);
pw.println(" Throttled by Type:");
final long compactionsThrottled = totalCompactRequested - totalCompactPerformed;
// Any throttle that was not part of the previous categories
final long unaccountedThrottled = compactionsThrottled
- mProcCompactionsNoPidThrottled - mProcCompactionsOomAdjThrottled
- mProcCompactionsTimeThrottled - mProcCompactionsRSSThrottled
- mProcCompactionsMiscThrottled;
pw.println(" NoPid: " + mProcCompactionsNoPidThrottled
+ " OomAdj: " + mProcCompactionsOomAdjThrottled + " Time: "
+ mProcCompactionsTimeThrottled + " RSS: " + mProcCompactionsRSSThrottled
+ " Misc: " + mProcCompactionsMiscThrottled
+ " Unaccounted: " + unaccountedThrottled);
final double compactThrottlePercentage =
(compactionsThrottled / (double) totalCompactRequested) * 100.0;
pw.println(" Throttle Percentage: " + compactThrottlePercentage);
}
if (mFullCompactPerformed > 0) {
pw.println(" -----Memory Stats----");
pw.println(" Total Delta Anon RSS (KB) : " + mTotalDeltaAnonRssKBs);
pw.println(" Total Physical ZRAM Consumed (KB): " + mTotalZramConsumedKBs);
pw.println(" Total Anon Memory Freed (KB): " + mTotalAnonMemFreedKBs);
// This tells us how much anon memory we were able to free thanks to running
// compaction
pw.println(" Avg Compaction Efficiency (Anon Freed/Anon RSS): "
+ (mTotalAnonMemFreedKBs / (double) mSumOrigAnonRss));
pw.println(" Max Compaction Efficiency: " + mMaxCompactEfficiency);
// This tells us how effective is the compression algorithm in physical memory
pw.println(" Avg Compression Ratio (1 - ZRAM Consumed/DeltaAnonRSS): "
+ (1.0 - mTotalZramConsumedKBs / (double) mTotalDeltaAnonRssKBs));
long avgKBsPerProcCompact = mFullCompactPerformed > 0
? (mTotalAnonMemFreedKBs / mFullCompactPerformed)
: 0;
pw.println(" Avg Anon Mem Freed/Compaction (KB) : " + avgKBsPerProcCompact);
double compactionCost =
mTotalCpuTimeMillis / (mTotalAnonMemFreedKBs / 1024.0); // ms/MB
pw.println(" Compaction Cost (ms/MB): " + compactionCost);
}
}
}
class AggregatedProcessCompactionStats extends AggregatedCompactionStats {
public final String processName;
AggregatedProcessCompactionStats(String processName) { this.processName = processName; }
}
class AggregatedSourceCompactionStats extends AggregatedCompactionStats {
public final CompactSource sourceType;
AggregatedSourceCompactionStats(CompactSource sourceType) { this.sourceType = sourceType; }
}
private final LinkedHashMap<String, AggregatedProcessCompactionStats> mPerProcessCompactStats =
new LinkedHashMap<>(256);
private final EnumMap<CompactSource, AggregatedSourceCompactionStats> mPerSourceCompactStats =
new EnumMap<>(CompactSource.class);
private long mTotalCompactionDowngrades;
private long mSystemCompactionsPerformed;
private long mSystemTotalMemFreed;
private EnumMap<CancelCompactReason, Integer> mTotalCompactionsCancelled =
new EnumMap<>(CancelCompactReason.class);
private final ProcessDependencies mProcessDependencies;
private final ProcLocksReader mProcLocksReader;
public CachedAppOptimizer(ActivityManagerService am) {
this(am, null, new DefaultProcessDependencies());
}
@VisibleForTesting
CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback,
ProcessDependencies processDependencies) {
mAm = am;
mProcLock = am.mProcLock;
mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
Process.THREAD_GROUP_SYSTEM, true);
mProcStateThrottle = new HashSet<>();
mProcessDependencies = processDependencies;
mTestCallback = callback;
mSettingsObserver = new SettingsContentObserver();
mProcLocksReader = new ProcLocksReader();
}
/**
* Reads phenotype config to determine whether app compaction is enabled or not and
* starts the background thread if necessary.
*/
public void init() {
// TODO: initialize flags to default and only update them if values are set in DeviceConfig
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
DeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
ActivityThread.currentApplication().getMainExecutor(),
mOnNativeBootFlagsChangedListener);
mAm.mContext.getContentResolver().registerContentObserver(
CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver);
synchronized (mPhenotypeFlagLock) {
updateUseCompaction();
updateCompactionThrottles();
updateCompactStatsdSampleRate();
updateFreezerStatsdSampleRate();
updateFullRssThrottle();
updateFullDeltaRssThrottle();
updateProcStateThrottle();
updateUseFreezer();
updateMinOomAdjThrottle();
updateMaxOomAdjThrottle();
}
}
/**
* Returns whether compaction is enabled.
*/
public boolean useCompaction() {
synchronized (mPhenotypeFlagLock) {
return mUseCompaction;
}
}
/**
* Returns whether freezer is enabled.
*/
public boolean useFreezer() {
synchronized (mPhenotypeFlagLock) {
return mUseFreezer;
}
}
/**
* Returns whether freezer exempts INSTALL_PACKAGES.
*/
public boolean freezerExemptInstPkg() {
synchronized (mPhenotypeFlagLock) {
return mUseFreezer && mFreezerExemptInstPkg;
}
}
@GuardedBy("mProcLock")
void dump(PrintWriter pw) {
pw.println("CachedAppOptimizer settings");
synchronized (mPhenotypeFlagLock) {
pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction);
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_MIN_OOM_ADJ + "=" + mCompactThrottleMinOomAdj);
pw.println(" " + KEY_COMPACT_THROTTLE_MAX_OOM_ADJ + "=" + mCompactThrottleMaxOomAdj);
pw.println(" " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mCompactStatsdSampleRate);
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(" Per-Process Compaction Stats");
long totalCompactPerformedSome = 0;
long totalCompactPerformedFull = 0;
for (AggregatedProcessCompactionStats stats : mPerProcessCompactStats.values()) {
pw.println("-----" + stats.processName + "-----");
totalCompactPerformedSome += stats.mSomeCompactPerformed;
totalCompactPerformedFull += stats.mFullCompactPerformed;
stats.dump(pw);
pw.println();
}
pw.println();
pw.println(" Per-Source Compaction Stats");
for (AggregatedSourceCompactionStats stats : mPerSourceCompactStats.values()) {
pw.println("-----" + stats.sourceType + "-----");
stats.dump(pw);
pw.println();
}
pw.println();
pw.println("Total Compactions Performed by profile: " + totalCompactPerformedSome
+ " some, " + totalCompactPerformedFull + " full");
pw.println("Total compactions downgraded: " + mTotalCompactionDowngrades);
pw.println("Total compactions cancelled by reason: ");
for (CancelCompactReason reason : mTotalCompactionsCancelled.keySet()) {
pw.println(" " + reason + ": " + mTotalCompactionsCancelled.get(reason));
}
pw.println();
pw.println(" System Compaction Memory Stats");
pw.println(" Compactions Performed: " + mSystemCompactionsPerformed);
pw.println(" Total Memory Freed (KB): " + mSystemTotalMemFreed);
double avgKBsPerSystemCompact = mSystemCompactionsPerformed > 0
? mSystemTotalMemFreed / mSystemCompactionsPerformed
: 0;
pw.println(" Avg Mem Freed per Compact (KB): " + avgKBsPerSystemCompact);
pw.println();
pw.println(" Tracking last compaction stats for " + mLastCompactionStats.size()
+ " processes.");
pw.println("Last Compaction per process stats:");
pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs,"
+ "CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,oomAdjReason)");
for (Map.Entry<Integer, SingleCompactionStats> entry :
mLastCompactionStats.entrySet()) {
SingleCompactionStats stats = entry.getValue();
stats.dump(pw);
}
pw.println();
pw.println("Last 20 Compactions Stats:");
pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs,"
+ "CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,oomAdjReason)");
for (SingleCompactionStats stats : mCompactionStatsHistory) {
stats.dump(pw);
}
pw.println();
pw.println(" " + KEY_USE_FREEZER + "=" + mUseFreezer);
pw.println(" " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate);
pw.println(" " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout);
pw.println(" " + KEY_FREEZER_EXEMPT_INST_PKG + "=" + mFreezerExemptInstPkg);
pw.println(" " + KEY_FREEZER_BINDER_ENABLED + "=" + mFreezerBinderEnabled);
pw.println(" " + KEY_FREEZER_BINDER_THRESHOLD + "=" + mFreezerBinderThreshold);
pw.println(" " + KEY_FREEZER_BINDER_DIVISOR + "=" + mFreezerBinderDivisor);
pw.println(" " + KEY_FREEZER_BINDER_OFFSET + "=" + mFreezerBinderOffset);
pw.println(" " + KEY_FREEZER_BINDER_CALLBACK_ENABLED + "="
+ mFreezerBinderCallbackEnabled);
pw.println(" " + KEY_FREEZER_BINDER_CALLBACK_THROTTLE + "="
+ mFreezerBinderCallbackThrottle);
pw.println(" " + KEY_FREEZER_BINDER_ASYNC_THRESHOLD + "="
+ mFreezerBinderAsyncThreshold);
synchronized (mProcLock) {
int size = mFrozenProcesses.size();
pw.println(" Apps frozen: " + size);
for (int i = 0; i < size; i++) {
ProcessRecord app = mFrozenProcesses.valueAt(i);
pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime() + ": " + app.getPid()
+ " " + app.processName
+ (app.mOptRecord.isFreezeSticky() ? " (sticky)" : ""));
}
if (!mPendingCompactionProcesses.isEmpty()) {
pw.println(" Pending compactions:");
size = mPendingCompactionProcesses.size();
for (int i = 0; i < size; i++) {
ProcessRecord app = mPendingCompactionProcesses.get(i);
pw.println(" pid: " + app.getPid() + ". name: " + app.processName
+ ". hasPendingCompact: " + app.mOptRecord.hasPendingCompact());
}
}
}
}
}
@GuardedBy("mProcLock")
boolean compactApp(
ProcessRecord app, CompactProfile compactProfile, CompactSource source, boolean force) {
app.mOptRecord.setReqCompactSource(source);
app.mOptRecord.setReqCompactProfile(compactProfile);
AggregatedSourceCompactionStats perSourceStats = getPerSourceAggregatedCompactStat(source);
AggregatedCompactionStats perProcStats =
getPerProcessAggregatedCompactStat(app.processName);
switch (compactProfile) {
case SOME:
++perProcStats.mSomeCompactRequested;
++perSourceStats.mSomeCompactRequested;
break;
case FULL:
++perProcStats.mFullCompactRequested;
++perSourceStats.mFullCompactRequested;
break;
default:
Slog.e(TAG_AM,
"Unimplemented compaction type, consider adding it.");
return false;
}
if (!app.mOptRecord.hasPendingCompact()) {
final String processName = (app.processName != null ? app.processName : "");
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
"compactApp " + app.mOptRecord.getReqCompactSource().name() + " "
+ app.mOptRecord.getReqCompactProfile().name() + " " + processName);
}
app.mOptRecord.setHasPendingCompact(true);
app.mOptRecord.setForceCompact(force);
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
return true;
}
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
" compactApp Skipped for " + app.processName + " pendingCompact= "
+ app.mOptRecord.hasPendingCompact() + ". Requested compact profile: "
+ app.mOptRecord.getReqCompactProfile().name() + ". Compact source "
+ app.mOptRecord.getReqCompactSource().name());
}
return false;
}
void compactNative(CompactProfile compactProfile, int pid) {
mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
COMPACT_NATIVE_MSG, pid, compactProfile.ordinal()));
}
private AggregatedProcessCompactionStats getPerProcessAggregatedCompactStat(
String processName) {
if (processName == null) {
processName = "";
}
AggregatedProcessCompactionStats stats = mPerProcessCompactStats.get(processName);
if (stats == null) {
stats = new AggregatedProcessCompactionStats(processName);
mPerProcessCompactStats.put(processName, stats);
}
return stats;
}
private AggregatedSourceCompactionStats getPerSourceAggregatedCompactStat(
CompactSource source) {
AggregatedSourceCompactionStats stats = mPerSourceCompactStats.get(source);
if (stats == null) {
stats = new AggregatedSourceCompactionStats(source);
mPerSourceCompactStats.put(source, stats);
}
return stats;
}
void compactAllSystem() {
if (useCompaction()) {
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM, "compactAllSystem");
}
Trace.instantForTrack(
Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, "compactAllSystem");
mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
COMPACT_SYSTEM_MSG));
}
}
private native void compactSystem();
/**
* Compacts a process or app
* @param pid pid of process to compact
* @param compactionFlags selects the compaction type as defined by COMPACT_ACTION_{TYPE}_FLAG
* constants
*/
static private native void compactProcess(int pid, int compactionFlags);
static private native void cancelCompaction();
/**
* Returns the cpu time for the current thread
*/
static private native long threadCpuTimeNs();
/**
* Retrieves the free swap percentage.
*/
static native double getFreeSwapPercent();
/**
* Retrieves the total used physical ZRAM
*/
static private native long getUsedZramMemory();
/**
* Amount of memory that has been made free due to compaction.
* It represents uncompressed memory size - compressed memory size.
*/
static private native long getMemoryFreedCompaction();
/**
* Reads the flag value from DeviceConfig to determine whether app compaction
* should be enabled, and starts the freeze/compaction thread if needed.
*/
@GuardedBy("mPhenotypeFlagLock")
private void updateUseCompaction() {
mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
if (mUseCompaction && mCompactionHandler == null) {
if (!mCachedAppOptimizerThread.isAlive()) {
mCachedAppOptimizerThread.start();
}
mCompactionHandler = new MemCompactionHandler();
Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
Process.THREAD_GROUP_SYSTEM);
}
}
/**
* Enables or disabled the app freezer.
* @param enable Enables the freezer if true, disables it if false.
* @return true if the operation completed successfully, false otherwise.
*/
public synchronized boolean enableFreezer(boolean enable) {
if (!mUseFreezer) {
return false;
}
if (enable) {
mFreezerDisableCount--;
if (mFreezerDisableCount > 0) {
return true;
} else if (mFreezerDisableCount < 0) {
Slog.e(TAG_AM, "unbalanced call to enableFreezer, ignoring");
mFreezerDisableCount = 0;
return false;
}
} else {
mFreezerDisableCount++;
if (mFreezerDisableCount > 1) {
return true;
}
}
// Override is applied immediately, restore is delayed
synchronized (mAm) {
synchronized (mProcLock) {
mFreezerOverride = !enable;
Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride);
mAm.mProcessList.forEachLruProcessesLOSP(true, process -> {
if (process == null) {
return;
}
final ProcessCachedOptimizerRecord opt = process.mOptRecord;
if (enable && opt.hasFreezerOverride()) {
freezeAppAsyncLSP(process);
opt.setFreezerOverride(false);
}
if (!enable && opt.isFrozen()) {
unfreezeAppLSP(process, UNFREEZE_REASON_FEATURE_FLAGS);
// Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
opt.setFreezerOverride(true);
}
});
}
}
return true;
}
/**
* Informs binder that a process is about to be frozen. If freezer is enabled on a process via
* this method, this method will synchronously dispatch all pending transactions to the
* specified pid. This method will not add significant latencies when unfreezing.
* After freezing binder calls, binder will block all transaction to the frozen pid, and return
* an error to the sending process.
*
* @param pid the target pid for which binder transactions are to be frozen
* @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze
* binder for the specificed pid.
* @param timeoutMs the timeout in milliseconds to wait for the binder interface to freeze
* before giving up.
*
* @throws RuntimeException in case a flush/freeze operation could not complete successfully.
* @return 0 if success, or -EAGAIN indicating there's pending transaction.
*/
public static native int freezeBinder(int pid, boolean freeze, int timeoutMs);
/**
* Retrieves binder freeze info about a process.
* @param pid the pid for which binder freeze info is to be retrieved.
*
* @throws RuntimeException if the operation could not complete successfully.
* @return a bit field reporting the binder freeze info for the process.
*/
private static native int getBinderFreezeInfo(int pid);
/**
* Returns the path to be checked to verify whether the freezer is supported by this system.
* @return absolute path to the file
*/
private static native String getFreezerCheckPath();
/**
* Check if task_profiles.json includes valid freezer profiles and actions
* @return false if there are invalid profiles or actions
*/
private static native boolean isFreezerProfileValid();
/**
* Determines whether the freezer is supported by this system
*/
public static boolean isFreezerSupported() {
boolean supported = false;
FileReader fr = null;
try {
String path = getFreezerCheckPath();
Slog.d(TAG_AM, "Checking cgroup freezer: " + path);
fr = new FileReader(path);
char state = (char) fr.read();
if (state == '1' || state == '0') {
// Also check freezer binder ioctl
Slog.d(TAG_AM, "Checking binder freezer ioctl");
getBinderFreezeInfo(Process.myPid());
// Check if task_profiles.json contains invalid profiles
Slog.d(TAG_AM, "Checking freezer profiles");
supported = isFreezerProfileValid();
} else {
Slog.e(TAG_AM, "Unexpected value in cgroup.freeze");
}
} catch (java.io.FileNotFoundException e) {
Slog.w(TAG_AM, "File cgroup.freeze not present");
} catch (RuntimeException e) {
Slog.w(TAG_AM, "Unable to read freezer info");
} catch (Exception e) {
Slog.w(TAG_AM, "Unable to read cgroup.freeze: " + e.toString());
}
if (fr != null) {
try {
fr.close();
} catch (java.io.IOException e) {
Slog.e(TAG_AM, "Exception closing cgroup.freeze: " + e.toString());
}
}
Slog.d(TAG_AM, "Freezer supported: " + supported);
return supported;
}
/**
* Reads the flag value from DeviceConfig to determine whether app freezer
* should be enabled, and starts the freeze/compaction thread if needed.
*/
@GuardedBy("mPhenotypeFlagLock")
private void updateUseFreezer() {
final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(),
Settings.Global.CACHED_APPS_FREEZER_ENABLED);
if ("disabled".equals(configOverride)) {
mUseFreezer = false;
} else if ("enabled".equals(configOverride)
|| DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
mUseFreezer = isFreezerSupported();
updateFreezerDebounceTimeout();
updateFreezerExemptInstPkg();
} else {
mUseFreezer = false;
}
final boolean useFreezer = mUseFreezer;
// enableFreezer() would need the global ActivityManagerService lock, post it.
mAm.mHandler.post(() -> {
if (useFreezer) {
Slog.d(TAG_AM, "Freezer enabled");
enableFreezer(true);
if (!mCachedAppOptimizerThread.isAlive()) {
mCachedAppOptimizerThread.start();
}
if (mFreezeHandler == null) {
mFreezeHandler = new FreezeHandler();
}
Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
Process.THREAD_GROUP_SYSTEM);
} else {
Slog.d(TAG_AM, "Freezer disabled");
enableFreezer(false);
}
});
}
@GuardedBy("mPhenotypeFlagLock")
private void updateCompactionThrottles() {
boolean useThrottleDefaults = false;
// TODO: improve efficiency by calling DeviceConfig only once for all flags.
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);
String throttleMinOomAdjFlag =
DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_COMPACT_THROTTLE_MIN_OOM_ADJ);
String throttleMaxOomAdjFlag =
DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_COMPACT_THROTTLE_MAX_OOM_ADJ);
if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
|| TextUtils.isEmpty(throttleFullSomeFlag)
|| TextUtils.isEmpty(throttleFullFullFlag)
|| TextUtils.isEmpty(throttleBFGSFlag)
|| TextUtils.isEmpty(throttlePersistentFlag)
|| TextUtils.isEmpty(throttleMinOomAdjFlag)
|| TextUtils.isEmpty(throttleMaxOomAdjFlag)) {
// 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);
mCompactThrottleMinOomAdj = Long.parseLong(throttleMinOomAdjFlag);
mCompactThrottleMaxOomAdj = Long.parseLong(throttleMaxOomAdjFlag);
} 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;
mCompactThrottleMinOomAdj = DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
mCompactThrottleMaxOomAdj = DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ;
}
}
@GuardedBy("mPhenotypeFlagLock")
private void updateCompactStatsdSampleRate() {
mCompactStatsdSampleRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_COMPACT_STATSD_SAMPLE_RATE, DEFAULT_STATSD_SAMPLE_RATE);
mCompactStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mCompactStatsdSampleRate));
}
@GuardedBy("mPhenotypeFlagLock")
private void updateFreezerStatsdSampleRate() {
mFreezerStatsdSampleRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_FREEZER_STATSD_SAMPLE_RATE, DEFAULT_STATSD_SAMPLE_RATE);
mFreezerStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mFreezerStatsdSampleRate));
}
@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);
}
}
}
@GuardedBy("mPhenotypeFlagLock")
private void updateMinOomAdjThrottle() {
mCompactThrottleMinOomAdj = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_COMPACT_THROTTLE_MIN_OOM_ADJ, DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ);
// Should only compact cached processes.
if (mCompactThrottleMinOomAdj < ProcessList.CACHED_APP_MIN_ADJ) {
mCompactThrottleMinOomAdj = DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
}
}
@GuardedBy("mPhenotypeFlagLock")
private void updateMaxOomAdjThrottle() {
mCompactThrottleMaxOomAdj = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_COMPACT_THROTTLE_MAX_OOM_ADJ, DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ);
// Should only compact cached processes.
if (mCompactThrottleMaxOomAdj > ProcessList.CACHED_APP_MAX_ADJ) {
mCompactThrottleMaxOomAdj = DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ;
}
}
@GuardedBy("mPhenotypeFlagLock")
private void updateFreezerDebounceTimeout() {
mFreezerDebounceTimeout = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_FREEZER_DEBOUNCE_TIMEOUT, DEFAULT_FREEZER_DEBOUNCE_TIMEOUT);
if (mFreezerDebounceTimeout < 0) {
mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
}
Slog.d(TAG_AM, "Freezer timeout set to " + mFreezerDebounceTimeout);
}
@GuardedBy("mPhenotypeFlagLock")
private void updateFreezerExemptInstPkg() {
mFreezerExemptInstPkg = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_FREEZER_EXEMPT_INST_PKG, DEFAULT_FREEZER_EXEMPT_INST_PKG);
Slog.d(TAG_AM, "Freezer exemption set to " + mFreezerExemptInstPkg);
}
@GuardedBy("mPhenotypeFlagLock")
private void updateFreezerBinderState() {
mFreezerBinderEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_FREEZER_BINDER_ENABLED, DEFAULT_FREEZER_BINDER_ENABLED);
mFreezerBinderDivisor = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_FREEZER_BINDER_DIVISOR, DEFAULT_FREEZER_BINDER_DIVISOR);
mFreezerBinderOffset = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_FREEZER_BINDER_OFFSET, DEFAULT_FREEZER_BINDER_OFFSET);
mFreezerBinderThreshold = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_FREEZER_BINDER_THRESHOLD, DEFAULT_FREEZER_BINDER_THRESHOLD);
mFreezerBinderCallbackEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_FREEZER_BINDER_CALLBACK_ENABLED, DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED);
mFreezerBinderCallbackThrottle = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_FREEZER_BINDER_CALLBACK_THROTTLE, DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE);
mFreezerBinderAsyncThreshold = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_FREEZER_BINDER_ASYNC_THRESHOLD, DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD);
Slog.d(TAG_AM, "Freezer binder state set to enabled=" + mFreezerBinderEnabled
+ ", divisor=" + mFreezerBinderDivisor
+ ", offset=" + mFreezerBinderOffset
+ ", threshold=" + mFreezerBinderThreshold
+ ", callback enabled=" + mFreezerBinderCallbackEnabled
+ ", callback throttle=" + mFreezerBinderCallbackThrottle
+ ", async threshold=" + mFreezerBinderAsyncThreshold);
}
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;
}
/**
* Returns the earliest time (relative) from now that the app can be frozen.
* @param app The app to update
* @param delayMillis How much to delay freezing by
*/
@GuardedBy("mProcLock")
private long updateEarliestFreezableTime(ProcessRecord app, long delayMillis) {
final long now = SystemClock.uptimeMillis();
app.mOptRecord.setEarliestFreezableTime(
Math.max(app.mOptRecord.getEarliestFreezableTime(), now + delayMillis));
return app.mOptRecord.getEarliestFreezableTime() - now;
}
// This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
@GuardedBy("mAm")
void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason) {
unfreezeTemporarily(app, reason, mFreezerDebounceTimeout);
}
// This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
@GuardedBy("mAm")
void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason, long delayMillis) {
if (mUseFreezer) {
synchronized (mProcLock) {
// Move the earliest freezable time further, if necessary
final long delay = updateEarliestFreezableTime(app, delayMillis);
if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
unfreezeAppLSP(app, reason);
freezeAppAsyncLSP(app, delay);
}
}
}
}
@GuardedBy({"mAm", "mProcLock"})
void freezeAppAsyncLSP(ProcessRecord app) {
freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, mFreezerDebounceTimeout));
}
@GuardedBy({"mAm", "mProcLock"})
private void freezeAppAsyncLSP(ProcessRecord app, @UptimeMillisLong long delayMillis) {
freezeAppAsyncInternalLSP(app, delayMillis, false);
}
@GuardedBy({"mAm", "mProcLock"})
void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis,
boolean force) {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
if (opt.isPendingFreeze()) {
// Skip redundant DO_FREEZE message
return;
}
if (opt.isFreezeSticky() && !force) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM,
"Skip freezing because unfrozen state is sticky pid=" + app.getPid() + " "
+ app.processName);
}
return;
}
if (mAm.mConstants.USE_MODERN_TRIM
&& app.mState.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) {
final IApplicationThread thread = app.getThread();
if (thread != null) {
try {
thread.scheduleTrimMemory(TRIM_MEMORY_BACKGROUND);
} catch (RemoteException e) {
// do nothing
}
}
}
reportProcessFreezableChangedLocked(app);
app.mOptRecord.setLastUsedTimeout(delayMillis);
mFreezeHandler.sendMessageDelayed(
mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
delayMillis);
opt.setPendingFreeze(true);
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Async freezing " + app.getPid() + " " + app.processName);
}
}
@GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
final int pid = app.getPid();
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
boolean sticky = opt.isFreezeSticky();
if (sticky && !force) {
// Sticky freezes will not change their state unless forced out of it.
if (DEBUG_FREEZER) {
Slog.d(TAG_AM,
"Skip unfreezing because frozen state is sticky pid=" + pid + " "
+ app.processName);
}
return;
}
boolean processFreezableChangeReported = false;
if (opt.isPendingFreeze()) {
// Remove pending DO_FREEZE message
mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
opt.setPendingFreeze(false);
reportProcessFreezableChangedLocked(app);
processFreezableChangeReported = true;
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Cancel freezing " + pid + " " + app.processName);
}
}
UidRecord uidRec = app.getUidRecord();
if (uidRec != null && uidRec.isFrozen()) {
uidRec.setFrozen(false);
postUidFrozenMessage(uidRec.getUid(), false);
}
opt.setFreezerOverride(false);
if (pid == 0 || !opt.isFrozen()) {
return;
}
// Unfreeze the binder interface first, to avoid transactions triggered by timers fired
// right after unfreezing the process to fail
boolean processKilled = false;
try {
int freezeInfo = getBinderFreezeInfo(pid);
if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
Slog.d(TAG_AM, "pid " + pid + " " + app.processName
+ " received sync transactions while frozen, killing");
app.killLocked("Sync transaction while in frozen state",
ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION, true);
processKilled = true;
}
if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) {
Slog.d(TAG_AM, "pid " + pid + " " + app.processName
+ " received async transactions while frozen");
}
} catch (Exception e) {
Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + pid + " "
+ app.processName + ". Killing it. Exception: " + e);
app.killLocked("Unable to query binder frozen stats",
ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
processKilled = true;
}
if (processKilled) {
return;
}
if (!processFreezableChangeReported) {
reportProcessFreezableChangedLocked(app);
}
long freezeTime = opt.getFreezeUnfreezeTime();
try {
freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
+ ". Killing it");
app.killLocked("Unable to unfreeze",
ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
return;
}
try {
traceAppFreeze(app.processName, pid, reason);
Process.setProcessFrozen(pid, app.uid, false);
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
opt.setFrozen(false);
mFrozenProcesses.delete(pid);
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to unfreeze " + pid + " " + app.processName
+ ". This might cause inconsistency or UI hangs.");
}
if (!opt.isFrozen()) {
Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName + " for " + reason);
mFreezeHandler.sendMessage(
mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
pid,
(int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
new Pair<ProcessRecord, Integer>(app, reason)));
}
}
@GuardedBy({"mAm", "mProcLock"})
void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
synchronized (mFreezerLock) {
unfreezeAppInternalLSP(app, reason, false);
}
}
/**
* This quick function works around the race condition between WM/ATMS and AMS, allowing
* the former to directly unfreeze a frozen process before the latter runs updateOomAdj.
* After the race issue is solved, this workaround can be removed. (b/213288355)
* The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
* @param pid pid of the process to be unfrozen
*/
void unfreezeProcess(int pid, @OomAdjReason int reason) {
synchronized (mFreezerLock) {
ProcessRecord app = mFrozenProcesses.get(pid);
if (app == null) {
return;
}
Slog.d(TAG_AM, "quick sync unfreeze " + pid + " for " + reason);
try {
freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to quick unfreeze binder for " + pid);
return;
}
try {
traceAppFreeze(app.processName, pid, reason);
Process.setProcessFrozen(pid, app.uid, false);
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
}
}
}
/**
* Trace app freeze status
* @param processName The name of the target process
* @param pid The pid of the target process
* @param reason UNFREEZE_REASON_XXX (>=0) for unfreezing and -1 for freezing
*/
private static void traceAppFreeze(String processName, int pid, int reason) {
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK,
(reason < 0 ? "Freeze " : "Unfreeze ") + processName + ":" + pid + " " + reason);
}
/**
* To be called when the given app is killed.
*/
@GuardedBy({"mAm", "mProcLock"})
void onCleanupApplicationRecordLocked(ProcessRecord app) {
if (mUseFreezer) {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
if (opt.isPendingFreeze()) {
// Remove pending DO_FREEZE message
mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
opt.setPendingFreeze(false);
}
final UidRecord uidRec = app.getUidRecord();
if (uidRec != null) {
final boolean isFrozen = uidRec.getNumOfProcs() > 1
&& uidRec.areAllProcessesFrozen(app);
if (isFrozen != uidRec.isFrozen()) {
uidRec.setFrozen(isFrozen);
postUidFrozenMessage(uidRec.getUid(), isFrozen);
}
}
mFrozenProcesses.delete(app.getPid());
}
}
void onWakefulnessChanged(int wakefulness) {
if(wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
if (useCompaction()) {
// Remove any pending compaction we may have scheduled to happen while screen was
// off
cancelAllCompactions(CancelCompactReason.SCREEN_ON);
}
}
}
void cancelAllCompactions(CancelCompactReason reason) {
synchronized (mProcLock) {
while(!mPendingCompactionProcesses.isEmpty()) {
if (DEBUG_COMPACTION) {
Slog.e(TAG_AM,
"Cancel pending compaction as system is awake for process="
+ mPendingCompactionProcesses.get(0).processName);
}
cancelCompactionForProcess(mPendingCompactionProcesses.get(0), reason);
}
mPendingCompactionProcesses.clear();
}
}
@GuardedBy("mProcLock")
void cancelCompactionForProcess(ProcessRecord app, CancelCompactReason cancelReason) {
boolean cancelled = false;
if (mPendingCompactionProcesses.contains(app)) {
app.mOptRecord.setHasPendingCompact(false);
mPendingCompactionProcesses.remove(app);
cancelled = true;
}
if (DefaultProcessDependencies.mPidCompacting == app.mPid) {
cancelCompaction();
cancelled = true;
}
if (cancelled) {
if (mTotalCompactionsCancelled.containsKey(cancelReason)) {
int count = mTotalCompactionsCancelled.get(cancelReason);
mTotalCompactionsCancelled.put(cancelReason, count + 1);
} else {
mTotalCompactionsCancelled.put(cancelReason, 1);
}
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
"Cancelled pending or running compactions for process: " +
app.processName != null ? app.processName : "" +
" reason: " + cancelReason.name());
}
}
}
@GuardedBy({"mService", "mProcLock"})
void onOomAdjustChanged(int oldAdj, int newAdj, ProcessRecord app) {
if (useCompaction()) {
// Cancel any currently executing compactions
// if the process moved out of cached state
if (newAdj < oldAdj && newAdj < ProcessList.CACHED_APP_MIN_ADJ) {
cancelCompactionForProcess(app, CancelCompactReason.OOM_IMPROVEMENT);
}
}
}
/**
* Callback received after a process has been frozen.
*/
void onProcessFrozen(ProcessRecord frozenProc) {
if (useCompaction()) {
synchronized (mProcLock) {
compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false);
}
}
}
/**
* Computes the final compaction profile to be used which depends on compaction
* features enabled and swap usage.
*/
CompactProfile resolveCompactionProfile(CompactProfile profile) {
if (profile == CompactProfile.FULL) {
double swapFreePercent = getFreeSwapPercent();
// Downgrade compaction under swap memory pressure
if (swapFreePercent < COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD) {
profile = CompactProfile.SOME;
++mTotalCompactionDowngrades;
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
"Downgraded compaction to "+ profile +" due to low swap."
+ " Swap Free% " + swapFreePercent);
}
}
}
if (!ENABLE_SHARED_AND_CODE_COMPACT) {
if (profile == CompactProfile.SOME) {
profile = CompactProfile.NONE;
} else if (profile == CompactProfile.FULL) {
profile = CompactProfile.ANON;
}
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
"Final compaction profile "+ profile +" due to file compact disabled");
}
}
return profile;
}
boolean isProcessFrozen(int pid) {
synchronized (mProcLock) {
return mFrozenProcesses.contains(pid);
}
}
@VisibleForTesting
static final class SingleCompactionStats {
private static final float STATSD_SAMPLE_RATE = 0.1f;
private static final Random mRandom = new Random();
private final long[] mRssAfterCompaction;
public CompactSource mSourceType;
public String mProcessName;
public final int mUid;
public long mDeltaAnonRssKBs;
public long mZramConsumedKBs;
public long mAnonMemFreedKBs;
public float mCpuTimeMillis;
public long mOrigAnonRss;
public int mProcState;
public int mOomAdj;
public @OomAdjReason int mOomAdjReason;
SingleCompactionStats(long[] rss, CompactSource source, String processName,
long deltaAnonRss, long zramConsumed, long anonMemFreed, long origAnonRss,
long cpuTimeMillis, int procState, int oomAdj,
@OomAdjReason int oomAdjReason, int uid) {
mRssAfterCompaction = rss;
mSourceType = source;
mProcessName = processName;
mUid = uid;
mDeltaAnonRssKBs = deltaAnonRss;
mZramConsumedKBs = zramConsumed;
mAnonMemFreedKBs = anonMemFreed;
mCpuTimeMillis = cpuTimeMillis;
mOrigAnonRss = origAnonRss;
mProcState = procState;
mOomAdj = oomAdj;
mOomAdjReason = oomAdjReason;
}
double getCompactEfficiency() { return mAnonMemFreedKBs / (double) mOrigAnonRss; }
double getCompactCost() {
// mCpuTimeMillis / (anonMemFreedKBs/1024) and metric is in (ms/MB)
return mCpuTimeMillis / (double) mAnonMemFreedKBs * 1024;
}
long[] getRssAfterCompaction() {
return mRssAfterCompaction;
}
void dump(PrintWriter pw) {
pw.println(" (" + mProcessName + "," + mSourceType.name() + "," + mDeltaAnonRssKBs
+ "," + mZramConsumedKBs + "," + mAnonMemFreedKBs + "," + getCompactEfficiency()
+ "," + getCompactCost() + "," + mProcState + "," + mOomAdj + ","
+ OomAdjuster.oomAdjReasonToString(mOomAdjReason) + ")");
}
void sendStat() {
if (mRandom.nextFloat() < STATSD_SAMPLE_RATE) {
FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPACTED_V2, mUid, mProcState,
mOomAdj, mDeltaAnonRssKBs, mZramConsumedKBs, mCpuTimeMillis, mOrigAnonRss,
mOomAdjReason);
}
}
}
private final class MemCompactionHandler extends Handler {
private MemCompactionHandler() {
super(mCachedAppOptimizerThread.getLooper());
}
private boolean shouldOomAdjThrottleCompaction(ProcessRecord proc) {
final String name = proc.processName;
// don't compact if the process has returned to perceptible
// and this is only a cached/home/prev compaction
if (proc.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ) {
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
"Skipping compaction as process " + name + " is "
+ "now perceptible.");
}
return true;
}
return false;
}
private boolean shouldTimeThrottleCompaction(ProcessRecord proc, long start,
CompactProfile pendingProfile, CompactSource source) {
final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
final String name = proc.processName;
CompactProfile lastCompactProfile = opt.getLastCompactProfile();
long lastCompactTime = opt.getLastCompactTime();
// basic throttling
// use the Phenotype flag knobs to determine whether current/previous
// compaction combo should be throttled 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 (source == CompactSource.APP) {
if (pendingProfile == CompactProfile.SOME) {
if ((lastCompactProfile == CompactProfile.SOME
&& (start - lastCompactTime < mCompactThrottleSomeSome))
|| (lastCompactProfile == CompactProfile.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 true;
}
} else if (pendingProfile == CompactProfile.FULL) {
if ((lastCompactProfile == CompactProfile.SOME
&& (start - lastCompactTime < mCompactThrottleFullSome))
|| (lastCompactProfile == CompactProfile.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 true;
}
}
}
}
return false;
}
private boolean shouldThrottleMiscCompaction(ProcessRecord proc, int procState) {
if (mProcStateThrottle.contains(procState)) {
if (DEBUG_COMPACTION) {
final String name = proc.processName;
Slog.d(TAG_AM,
"Skipping full compaction for process " + name + "; proc state is "
+ procState);
}
return true;
}
return false;
}
private boolean shouldRssThrottleCompaction(
CompactProfile profile, int pid, String name, long[] rssBefore) {
long anonRssBefore = rssBefore[RSS_ANON_INDEX];
SingleCompactionStats lastCompactionStats = mLastCompactionStats.get(pid);
if (rssBefore[RSS_TOTAL_INDEX] == 0 && rssBefore[RSS_FILE_INDEX] == 0
&& rssBefore[RSS_ANON_INDEX] == 0 && rssBefore[RSS_SWAP_INDEX] == 0) {
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
"Skipping compaction for"
+ "process " + pid + " with no memory usage. Dead?");
}
return true;
}
if (profile == CompactProfile.FULL) {
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 true;
}
if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
long[] lastRss = lastCompactionStats.getRssAfterCompaction();
long absDelta = Math.abs(rssBefore[RSS_FILE_INDEX] - lastRss[RSS_FILE_INDEX])
+ Math.abs(rssBefore[RSS_ANON_INDEX] - lastRss[RSS_ANON_INDEX])
+ Math.abs(rssBefore[RSS_SWAP_INDEX] - lastRss[RSS_SWAP_INDEX]);
if (absDelta <= mFullDeltaRssThrottleKb) {
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
"Skipping full compaction for process " + name
+ "; abs delta is too small: " + absDelta + "KB.");
}
return true;
}
}
}
return false;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case COMPACT_PROCESS_MSG: {
long start = SystemClock.uptimeMillis();
ProcessRecord proc;
final ProcessCachedOptimizerRecord opt;
int pid;
final String name;
CompactProfile lastCompactProfile;
long lastCompactTime;
int newOomAdj = msg.arg1;
int procState = msg.arg2;
boolean forceCompaction;
CompactSource compactSource;
CompactProfile requestedProfile;
int oomAdjReason;
synchronized (mProcLock) {
if (mPendingCompactionProcesses.isEmpty()) {
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM, "No processes pending compaction, bail out");
}
return;
}
proc = mPendingCompactionProcesses.remove(0);
opt = proc.mOptRecord;
forceCompaction = opt.isForceCompact();
opt.setForceCompact(false); // since this is a one-shot operation
pid = proc.getPid();
name = proc.processName;
opt.setHasPendingCompact(false);
compactSource = opt.getReqCompactSource();
requestedProfile = opt.getReqCompactProfile();
lastCompactProfile = opt.getLastCompactProfile();
lastCompactTime = opt.getLastCompactTime();
oomAdjReason = opt.getLastOomAdjChangeReason();
}
AggregatedSourceCompactionStats perSourceStats =
getPerSourceAggregatedCompactStat(opt.getReqCompactSource());
AggregatedProcessCompactionStats perProcessStats =
getPerProcessAggregatedCompactStat(name);
long[] rssBefore;
if (pid == 0) {
// not a real process, either one being launched or one being killed
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM, "Compaction failed, pid is 0");
}
++perSourceStats.mProcCompactionsNoPidThrottled;
++perProcessStats.mProcCompactionsNoPidThrottled;
return;
}
if (!forceCompaction) {
if (shouldOomAdjThrottleCompaction(proc)) {
++perProcessStats.mProcCompactionsOomAdjThrottled;
++perSourceStats.mProcCompactionsOomAdjThrottled;
return;
}
if (shouldTimeThrottleCompaction(
proc, start, requestedProfile, compactSource)) {
++perProcessStats.mProcCompactionsTimeThrottled;
++perSourceStats.mProcCompactionsTimeThrottled;
return;
}
if (shouldThrottleMiscCompaction(proc, procState)) {
++perProcessStats.mProcCompactionsMiscThrottled;
++perSourceStats.mProcCompactionsMiscThrottled;
return;
}
rssBefore = mProcessDependencies.getRss(pid);
if (shouldRssThrottleCompaction(requestedProfile, pid, name, rssBefore)) {
++perProcessStats.mProcCompactionsRSSThrottled;
++perSourceStats.mProcCompactionsRSSThrottled;
return;
}
} else {
rssBefore = mProcessDependencies.getRss(pid);
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM, "Forcing compaction for " + name);
}
}
CompactProfile resolvedProfile =
resolveCompactionProfile(requestedProfile);
if (resolvedProfile == CompactProfile.NONE) {
// No point on issuing compaction call as we don't want to compact.
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM, "Resolved no compaction for "+ name +
" requested profile="+requestedProfile);
}
return;
}
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"Compact " + resolvedProfile.name() + ": " + name
+ " lastOomAdjReason: " + oomAdjReason
+ " source: " + compactSource.name());
long zramUsedKbBefore = getUsedZramMemory();
long startCpuTime = threadCpuTimeNs();
mProcessDependencies.performCompaction(resolvedProfile, pid);
long endCpuTime = threadCpuTimeNs();
long[] rssAfter = mProcessDependencies.getRss(pid);
long end = SystemClock.uptimeMillis();
long time = end - start;
long deltaCpuTimeNanos = endCpuTime - startCpuTime;
long zramUsedKbAfter = getUsedZramMemory();
long deltaTotalRss = rssAfter[RSS_TOTAL_INDEX] - rssBefore[RSS_TOTAL_INDEX];
long deltaFileRss = rssAfter[RSS_FILE_INDEX] - rssBefore[RSS_FILE_INDEX];
long deltaAnonRss = rssAfter[RSS_ANON_INDEX] - rssBefore[RSS_ANON_INDEX];
long deltaSwapRss = rssAfter[RSS_SWAP_INDEX] - rssBefore[RSS_SWAP_INDEX];
switch (opt.getReqCompactProfile()) {
case SOME:
++perSourceStats.mSomeCompactPerformed;
++perProcessStats.mSomeCompactPerformed;
break;
case FULL:
++perSourceStats.mFullCompactPerformed;
++perProcessStats.mFullCompactPerformed;
long anonRssSavings = -deltaAnonRss;
long zramConsumed = zramUsedKbAfter - zramUsedKbBefore;
long memFreed = anonRssSavings - zramConsumed;
long totalCpuTimeMillis = deltaCpuTimeNanos / 1000000;
long origAnonRss = rssBefore[RSS_ANON_INDEX];
// Negative stats would skew averages and will likely be due to
// noise of system doing other things so we put a floor at 0 to
// avoid negative values.
anonRssSavings = anonRssSavings > 0 ? anonRssSavings : 0;
zramConsumed = zramConsumed > 0 ? zramConsumed : 0;
memFreed = memFreed > 0 ? memFreed : 0;
perProcessStats.addMemStats(anonRssSavings, zramConsumed, memFreed,
origAnonRss, totalCpuTimeMillis);
perSourceStats.addMemStats(anonRssSavings, zramConsumed, memFreed,
origAnonRss, totalCpuTimeMillis);
SingleCompactionStats memStats = new SingleCompactionStats(rssAfter,
compactSource, name, anonRssSavings, zramConsumed, memFreed,
origAnonRss, totalCpuTimeMillis, procState, newOomAdj,
oomAdjReason, proc.uid);
mLastCompactionStats.remove(pid);
mLastCompactionStats.put(pid, memStats);
mCompactionStatsHistory.add(memStats);
if (!forceCompaction) {
// Avoid polluting field metrics with forced compactions.
memStats.sendStat();
}
break;
default:
// We likely missed adding this category, it needs to be added
// if we end up here. In the meantime, gracefully fallback to
// attribute to app.
Slog.wtf(TAG_AM, "Compaction: Unknown requested action");
return;
}
EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name,
resolvedProfile.name(), rssBefore[RSS_TOTAL_INDEX],
rssBefore[RSS_FILE_INDEX], rssBefore[RSS_ANON_INDEX],
rssBefore[RSS_SWAP_INDEX], deltaTotalRss, deltaFileRss,
deltaAnonRss, deltaSwapRss, time, lastCompactProfile.name(),
lastCompactTime, newOomAdj, procState, zramUsedKbBefore,
zramUsedKbBefore - zramUsedKbAfter);
synchronized (mProcLock) {
opt.setLastCompactTime(end);
opt.setLastCompactProfile(requestedProfile);
}
} catch (Exception e) {
// nothing to do, presumably the process died
Slog.d(TAG_AM,
"Exception occurred while compacting pid: " + name
+ ". Exception:" + e.getMessage());
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
}
case COMPACT_SYSTEM_MSG: {
++mSystemCompactionsPerformed;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
long memFreedBefore = getMemoryFreedCompaction();
compactSystem();
long memFreedAfter = getMemoryFreedCompaction();
mSystemTotalMemFreed += memFreedAfter - memFreedBefore;
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
case COMPACT_NATIVE_MSG: {
int pid = msg.arg1;
CompactProfile compactProfile = CompactProfile.values()[msg.arg2];
Slog.d(TAG_AM,
"Performing native compaction for pid=" + pid
+ " type=" + compactProfile.name());
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
try {
mProcessDependencies.performCompaction(compactProfile, pid);
} catch (Exception e) {
Slog.d(TAG_AM, "Failed compacting native pid= " + pid);
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
}
}
}
private void reportOneUidFrozenStateChanged(int uid, boolean frozen) {
final int[] uids = new int[1];
final int[] frozenStates = new int[1];
uids[0] = uid;
frozenStates[0] = frozen ? UID_FROZEN_STATE_FROZEN : UID_FROZEN_STATE_UNFROZEN;
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "reportOneUidFrozenStateChanged uid " + uid + " frozen = " + frozen);
}
mAm.reportUidFrozenStateChanged(uids, frozenStates);
}
private void postUidFrozenMessage(int uid, boolean frozen) {
final Integer uidObj = Integer.valueOf(uid);
mFreezeHandler.removeEqualMessages(UID_FROZEN_STATE_CHANGED_MSG, uidObj);
final int op = frozen ? 1 : 0;
mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(UID_FROZEN_STATE_CHANGED_MSG, op,
0, uidObj));
}
@GuardedBy("mAm")
private void reportProcessFreezableChangedLocked(ProcessRecord app) {
mAm.onProcessFreezableChangedLocked(app);
}
private final class FreezeHandler extends Handler implements
ProcLocksReader.ProcLocksReaderCallback {
private FreezeHandler() {
super(mCachedAppOptimizerThread.getLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SET_FROZEN_PROCESS_MSG: {
ProcessRecord proc = (ProcessRecord) msg.obj;
synchronized (mAm) {
freezeProcess(proc);
}
if (proc.mOptRecord.isFrozen()) {
onProcessFrozen(proc);
removeMessages(DEADLOCK_WATCHDOG_MSG);
sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS);
}
} break;
case REPORT_UNFREEZE_MSG: {
int pid = msg.arg1;
int frozenDuration = msg.arg2;
Pair<ProcessRecord, Integer> obj = (Pair<ProcessRecord, Integer>) msg.obj;
ProcessRecord app = obj.first;
String processName = app.processName;
int reason = obj.second;
reportUnfreeze(app, pid, frozenDuration, processName, reason);
} break;
case UID_FROZEN_STATE_CHANGED_MSG: {
final boolean frozen = (msg.arg1 == 1);
final int uid = (int) msg.obj;
reportOneUidFrozenStateChanged(uid, frozen);
} break;
case DEADLOCK_WATCHDOG_MSG: {
try {
// post-check to prevent deadlock
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Freezer deadlock watchdog");
}
mProcLocksReader.handleBlockingFileLocks(this);
} catch (IOException e) {
Slog.w(TAG_AM, "Unable to check file locks");
}
} break;
case BINDER_ERROR_MSG: {
IntArray pids = new IntArray();
// Copy the frozen pids to a local array to release mProcLock ASAP
synchronized (mProcLock) {
int size = mFrozenProcesses.size();
for (int i = 0; i < size; i++) {
pids.add(mFrozenProcesses.keyAt(i));
}
}
// Check binder errors to frozen processes with a local freezer lock
synchronized (mFreezerLock) {
binderErrorLocked(pids);
}
} break;
default:
return;
}
}
@GuardedBy({"mAm", "mProcLock"})
private void handleBinderFreezerFailure(final ProcessRecord proc, final String reason) {
if (!mFreezerBinderEnabled) {
// Just reschedule indefinitely.
unfreezeAppLSP(proc, UNFREEZE_REASON_BINDER_TXNS);
freezeAppAsyncLSP(proc);
return;
}
/*
* This handles the case where a process couldn't be frozen due to pending binder
* transactions. In order to prevent apps from avoiding the freezer by spamming binder
* transactions, there is an exponential decrease in freezer retry times plus a random
* offset per attempt to avoid phase issues. Once the last-attempted timeout is below a
* threshold, we assume that the app is spamming binder calls and can never be frozen,
* and we will then crash the app.
*/
if (proc.mOptRecord.getLastUsedTimeout() <= mFreezerBinderThreshold) {
// We've given the app plenty of chances, assume broken. Time to die.
Slog.d(TAG_AM, "Kill app due to repeated failure to freeze binder: "
+ proc.getPid() + " " + proc.processName);
mAm.mHandler.post(() -> {
synchronized (mAm) {
// Crash regardless of procstate in case the app has found another way
// to abuse oom_adj
if (proc.getThread() == null) {
return;
}
proc.killLocked("excessive binder traffic during cached",
ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
true);
}
});
return;
}
long timeout = proc.mOptRecord.getLastUsedTimeout() / mFreezerBinderDivisor;
// range is [-mFreezerBinderOffset, +mFreezerBinderOffset]
int offset = mRandom.nextInt(mFreezerBinderOffset * 2) - mFreezerBinderOffset;
timeout = Math.max(timeout + offset, mFreezerBinderThreshold);
Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
+ " " + proc.processName + " (" + reason + "), timeout=" + timeout);
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK,
"Reschedule freeze " + proc.processName + ":" + proc.getPid()
+ " timeout=" + timeout + ", reason=" + reason);
unfreezeAppLSP(proc, UNFREEZE_REASON_BINDER_TXNS);
freezeAppAsyncLSP(proc, timeout);
}
/**
* Freeze a process.
* @param proc process to be frozen
*/
@GuardedBy({"mAm"})
private void freezeProcess(final ProcessRecord proc) {
int pid = proc.getPid(); // Unlocked intentionally
final String name = proc.processName;
final long unfrozenDuration;
final boolean frozen;
final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
synchronized (mProcLock) {
// someone has canceled this freeze
if (!opt.isPendingFreeze()) {
return;
}
opt.setPendingFreeze(false);
pid = proc.getPid();
if (mFreezerOverride) {
opt.setFreezerOverride(true);
Slog.d(TAG_AM, "Skipping freeze for process " + pid
+ " " + name + " curAdj = " + proc.mState.getCurAdj()
+ "(override)");
return;
}
if (pid == 0 || opt.isFrozen()) {
// Already frozen or not a real process, either one being
// launched or one being killed
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Skipping freeze for process " + pid
+ " " + name + ". Already frozen or not a real process");
}
return;
}
Slog.d(TAG_AM, "freezing " + pid + " " + name);
// Freeze binder interface before the process, to flush any
// transactions that might be pending.
try {
if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
handleBinderFreezerFailure(proc, "outstanding txns");
return;
}
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
mFreezeHandler.post(() -> {
synchronized (mAm) {
proc.killLocked("Unable to freeze binder interface",
ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
}
});
}
long unfreezeTime = opt.getFreezeUnfreezeTime();
try {
traceAppFreeze(proc.processName, pid, -1);
Process.setProcessFrozen(pid, proc.uid, true);
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
opt.setFrozen(true);
opt.setHasCollectedFrozenPSS(false);
mFrozenProcesses.put(pid, proc);
} catch (Exception e) {
Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
}
unfrozenDuration = opt.getFreezeUnfreezeTime() - unfreezeTime;
frozen = opt.isFrozen();
final UidRecord uidRec = proc.getUidRecord();
if (frozen && uidRec != null && uidRec.areAllProcessesFrozen()) {
uidRec.setFrozen(true);
postUidFrozenMessage(uidRec.getUid(), true);
}
}
if (!frozen) {
return;
}
EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);
// See above for why we're not taking mPhenotypeFlagLock here
if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
pid,
name,
unfrozenDuration,
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE,
UNFREEZE_REASON_NONE);
}
try {
// post-check to prevent races
int freezeInfo = getBinderFreezeInfo(pid);
if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
synchronized (mProcLock) {
handleBinderFreezerFailure(proc, "new pending txns");
}
return;
}
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
mFreezeHandler.post(() -> {
synchronized (mAm) {
proc.killLocked("Unable to freeze binder interface",
ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
}
});
}
}
private void reportUnfreeze(ProcessRecord app, int pid, int frozenDuration,
String processName, @UnfreezeReason int reason) {
EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason);
app.onProcessUnfrozen();
// See above for why we're not taking mPhenotypeFlagLock here
if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
FrameworkStatsLog.write(
FrameworkStatsLog.APP_FREEZE_CHANGED,
FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__UNFREEZE_APP,
pid,
processName,
frozenDuration,
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE, // deprecated
reason);
}
}
@GuardedBy({"mAm"})
@Override
public void onBlockingFileLock(IntArray pids) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Blocking file lock found: " + pids);
}
synchronized (mAm) {
synchronized (mProcLock) {
int pid = pids.get(0);
ProcessRecord app = mFrozenProcesses.get(pid);
ProcessRecord pr;
if (app != null) {
for (int i = 1; i < pids.size(); i++) {
int blocked = pids.get(i);
synchronized (mAm.mPidsSelfLocked) {
pr = mAm.mPidsSelfLocked.get(blocked);
}
if (pr != null
&& pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+ pr.processName + " (" + blocked + ")");
// Found at least one blocked non-cached process
unfreezeAppLSP(app, UNFREEZE_REASON_FILE_LOCKS);
break;
}
}
}
}
}
}
}
/**
* Default implementation for ProcessDependencies, public vor visibility to OomAdjuster class.
*/
private static final class DefaultProcessDependencies implements ProcessDependencies {
public static volatile int mPidCompacting = -1;
// Get memory RSS from process.
@Override
public long[] getRss(int pid) {
return Process.getRss(pid);
}
// Compact process.
@Override
public void performCompaction(CompactProfile profile, int pid) throws IOException {
mPidCompacting = pid;
if (profile == CompactProfile.FULL) {
compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG);
} else if (profile == CompactProfile.SOME) {
compactProcess(pid, COMPACT_ACTION_FILE_FLAG);
} else if (profile == CompactProfile.ANON) {
compactProcess(pid, COMPACT_ACTION_ANON_FLAG);
}
mPidCompacting = -1;
}
}
static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) {
switch (oomAdjReason) {
case OOM_ADJ_REASON_ACTIVITY:
return UNFREEZE_REASON_ACTIVITY;
case OOM_ADJ_REASON_FINISH_RECEIVER:
return UNFREEZE_REASON_FINISH_RECEIVER;
case OOM_ADJ_REASON_START_RECEIVER:
return UNFREEZE_REASON_START_RECEIVER;
case OOM_ADJ_REASON_BIND_SERVICE:
return UNFREEZE_REASON_BIND_SERVICE;
case OOM_ADJ_REASON_UNBIND_SERVICE:
return UNFREEZE_REASON_UNBIND_SERVICE;
case OOM_ADJ_REASON_START_SERVICE:
return UNFREEZE_REASON_START_SERVICE;
case OOM_ADJ_REASON_GET_PROVIDER:
return UNFREEZE_REASON_GET_PROVIDER;
case OOM_ADJ_REASON_REMOVE_PROVIDER:
return UNFREEZE_REASON_REMOVE_PROVIDER;
case OOM_ADJ_REASON_UI_VISIBILITY:
return UNFREEZE_REASON_UI_VISIBILITY;
case OOM_ADJ_REASON_ALLOWLIST:
return UNFREEZE_REASON_ALLOWLIST;
case OOM_ADJ_REASON_PROCESS_BEGIN:
return UNFREEZE_REASON_PROCESS_BEGIN;
case OOM_ADJ_REASON_PROCESS_END:
return UNFREEZE_REASON_PROCESS_END;
case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
return UNFREEZE_REASON_SHORT_FGS_TIMEOUT;
case OOM_ADJ_REASON_SYSTEM_INIT:
return UNFREEZE_REASON_SYSTEM_INIT;
case OOM_ADJ_REASON_BACKUP:
return UNFREEZE_REASON_BACKUP;
case OOM_ADJ_REASON_SHELL:
return UNFREEZE_REASON_SHELL;
case OOM_ADJ_REASON_REMOVE_TASK:
return UNFREEZE_REASON_REMOVE_TASK;
case OOM_ADJ_REASON_UID_IDLE:
return UNFREEZE_REASON_UID_IDLE;
case OOM_ADJ_REASON_STOP_SERVICE:
return UNFREEZE_REASON_STOP_SERVICE;
case OOM_ADJ_REASON_EXECUTING_SERVICE:
return UNFREEZE_REASON_EXECUTING_SERVICE;
case OOM_ADJ_REASON_RESTRICTION_CHANGE:
return UNFREEZE_REASON_RESTRICTION_CHANGE;
case OOM_ADJ_REASON_COMPONENT_DISABLED:
return UNFREEZE_REASON_COMPONENT_DISABLED;
default:
return UNFREEZE_REASON_NONE;
}
}
/**
* Kill a frozen process with a specified reason
*/
public void killProcess(int pid, String reason, @Reason int reasonCode,
@SubReason int subReason) {
mAm.mHandler.post(() -> {
synchronized (mAm) {
synchronized (mProcLock) {
ProcessRecord proc = mFrozenProcesses.get(pid);
// The process might have been killed or unfrozen by others
if (proc != null && proc.getThread() != null && !proc.isKilledByAm()) {
proc.killLocked(reason, reasonCode, subReason, true);
}
}
}
});
}
/**
* Sending binder transactions to frozen apps most likely indicates there's a bug. Log it and
* kill the frozen apps if they 1) receive sync binder transactions while frozen, or 2) miss
* async binder transactions due to kernel binder buffer running out.
*
* @param debugPid The binder transaction sender
* @param app The ProcessRecord of the sender
* @param code The binder transaction code
* @param flags The binder transaction flags
* @param err The binder transaction error
*/
public void binderError(int debugPid, ProcessRecord app, int code, int flags, int err) {
Slog.w(TAG_AM, "pid " + debugPid + " " + (app == null ? "null" : app.processName)
+ " sent binder code " + code + " with flags " + flags
+ " to frozen apps and got error " + err);
// Do nothing if the binder error callback is not enabled.
// That means the frozen apps in a wrong state will be killed when they are unfrozen later.
if (!mUseFreezer || !mFreezerBinderCallbackEnabled) {
return;
}
final long now = SystemClock.uptimeMillis();
if (now < mFreezerBinderCallbackLast + mFreezerBinderCallbackThrottle) {
Slog.d(TAG_AM, "Too many transaction errors, throttling freezer binder callback.");
return;
}
mFreezerBinderCallbackLast = now;
// Check all frozen processes in Freezer handler
mFreezeHandler.sendEmptyMessage(BINDER_ERROR_MSG);
}
private void binderErrorLocked(IntArray pids) {
// PIDs that run out of async binder buffer when being frozen
ArraySet<Integer> pidsAsync = (mFreezerBinderAsyncThreshold < 0) ? null : new ArraySet<>();
for (int i = 0; i < pids.size(); i++) {
int current = pids.get(i);
try {
int freezeInfo = getBinderFreezeInfo(current);
if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
killProcess(current, "Sync transaction while frozen",
ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION);
// No need to check async transactions in this case
continue;
}
if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
if (pidsAsync != null) {
pidsAsync.add(current);
}
if (DEBUG_FREEZER) {
Slog.w(TAG_AM, "pid " + current
+ " received async transactions while frozen");
}
}
} catch (Exception e) {
// The process has died. No need to kill it again.
Slog.w(TAG_AM, "Unable to query binder frozen stats for pid " + current);
}
}
// TODO: when kernel binder driver supports, poll the binder status directly.
// Binderfs stats, like other debugfs files, is not a reliable interface. But it's the
// only true source for now. The following code checks all frozen PIDs. If any of them
// is running out of async binder buffer, kill it. Otherwise it will be killed at a
// later time when AMS unfreezes it, which causes race issues.
if (pidsAsync == null || pidsAsync.size() == 0) {
return;
}
new BinderfsStatsReader().handleFreeAsyncSpace(
// Check if the frozen process has pending async calls
pidsAsync::contains,
// Kill the current process if it's running out of async binder space
(current, free) -> {
if (free < mFreezerBinderAsyncThreshold) {
Slog.w(TAG_AM, "pid " + current
+ " has " + free + " free async space, killing");
killProcess(current, "Async binder space running out while frozen",
ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_ASYNC_FULL);
}
},
// Log the error if binderfs stats can't be accesses or correctly parsed
exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats"));
}
}