| /* |
| * Copyright (C) 2020 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.PROCESS_STATE_NONEXISTENT; |
| import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; |
| import static android.os.Process.FIRST_APPLICATION_UID; |
| import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS; |
| import static android.view.Display.INVALID_DISPLAY; |
| |
| import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL; |
| import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW; |
| import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE; |
| import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL; |
| import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; |
| import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS; |
| import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RSS; |
| import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS; |
| import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RSS; |
| import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; |
| import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; |
| import static com.android.server.am.ActivityManagerService.DUMP_MEM_OOM_ADJ; |
| import static com.android.server.am.ActivityManagerService.DUMP_MEM_OOM_LABEL; |
| import static com.android.server.am.ActivityManagerService.GC_BACKGROUND_PROCESSES_MSG; |
| import static com.android.server.am.ActivityManagerService.KSM_SHARED; |
| import static com.android.server.am.ActivityManagerService.KSM_SHARING; |
| import static com.android.server.am.ActivityManagerService.KSM_UNSHARED; |
| import static com.android.server.am.ActivityManagerService.KSM_VOLATILE; |
| import static com.android.server.am.ActivityManagerService.REPORT_MEM_USAGE_MSG; |
| import static com.android.server.am.ActivityManagerService.appendBasicMemEntry; |
| import static com.android.server.am.ActivityManagerService.appendMemBucket; |
| import static com.android.server.am.ActivityManagerService.appendMemInfo; |
| import static com.android.server.am.ActivityManagerService.getKsmInfo; |
| import static com.android.server.am.ActivityManagerService.stringifyKBSize; |
| import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING; |
| import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; |
| import static com.android.server.wm.ActivityTaskManagerService.DUMP_ACTIVITIES_CMD; |
| |
| import android.annotation.BroadcastBehavior; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.ActivityManager; |
| import android.app.ActivityThread; |
| import android.app.IApplicationThread; |
| import android.app.ProfilerInfo; |
| import android.content.ComponentCallbacks2; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.net.Uri; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.Debug; |
| import android.os.Flags; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.os.SystemProperties; |
| import android.os.UserHandle; |
| import android.provider.DeviceConfig; |
| import android.provider.DeviceConfig.Properties; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.DebugUtils; |
| import android.util.FeatureFlagUtils; |
| import android.util.Pair; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.util.SparseIntArray; |
| import android.util.StatsEvent; |
| import android.util.proto.ProtoOutputStream; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.app.ProcessMap; |
| import com.android.internal.app.procstats.ProcessStats; |
| import com.android.internal.os.BackgroundThread; |
| import com.android.internal.os.BinderInternal; |
| import com.android.internal.os.ProcessCpuTracker; |
| import com.android.internal.util.DumpUtils; |
| import com.android.internal.util.FastPrintWriter; |
| import com.android.internal.util.FrameworkStatsLog; |
| import com.android.internal.util.MemInfoReader; |
| import com.android.internal.util.QuickSelect; |
| import com.android.server.am.LowMemDetector.MemFactor; |
| import com.android.server.power.stats.BatteryStatsImpl; |
| import com.android.server.utils.PriorityDump; |
| |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.function.Consumer; |
| import java.util.function.Predicate; |
| |
| /** |
| * A helper class taking care of the profiling, memory and cpu sampling of apps |
| */ |
| public class AppProfiler { |
| private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM; |
| |
| static final String TAG_PSS = TAG + POSTFIX_PSS; |
| static final String TAG_RSS = TAG + POSTFIX_RSS; |
| |
| static final String TAG_OOM_ADJ = ActivityManagerService.TAG_OOM_ADJ; |
| |
| /** Control over CPU and battery monitoring */ |
| // write battery stats every 30 minutes. |
| static final long BATTERY_STATS_TIME = 30 * 60 * 1000; |
| |
| static final boolean MONITOR_CPU_USAGE = true; |
| |
| // don't sample cpu less than every 5 seconds. |
| static final long MONITOR_CPU_MIN_TIME = 5 * 1000; |
| |
| // wait possibly forever for next cpu sample. |
| static final long MONITOR_CPU_MAX_TIME = 0x0fffffff; |
| |
| static final boolean MONITOR_THREAD_CPU_USAGE = false; |
| |
| static final String ACTIVITY_START_PSS_DEFER_CONFIG = "activity_start_pss_defer"; |
| |
| /** |
| * Broadcast sent when heap dump collection has been completed. |
| */ |
| @BroadcastBehavior(includeBackground = true, protectedBroadcast = true) |
| private static final String ACTION_HEAP_DUMP_FINISHED = |
| "com.android.internal.intent.action.HEAP_DUMP_FINISHED"; |
| |
| /** |
| * The process we are reporting |
| */ |
| private static final String EXTRA_HEAP_DUMP_PROCESS_NAME = |
| "com.android.internal.extra.heap_dump.PROCESS_NAME"; |
| |
| /** |
| * The size limit the process reached. |
| */ |
| private static final String EXTRA_HEAP_DUMP_SIZE_BYTES = |
| "com.android.internal.extra.heap_dump.SIZE_BYTES"; |
| |
| /** |
| * Whether the user initiated the dump or not. |
| */ |
| private static final String EXTRA_HEAP_DUMP_IS_USER_INITIATED = |
| "com.android.internal.extra.heap_dump.IS_USER_INITIATED"; |
| |
| /** |
| * Optional name of package to directly launch. |
| */ |
| private static final String EXTRA_HEAP_DUMP_REPORT_PACKAGE = |
| "com.android.internal.extra.heap_dump.REPORT_PACKAGE"; |
| |
| /** |
| * How long we defer PSS gathering while activities are starting, in milliseconds. |
| * This is adjustable via DeviceConfig. If it is zero or negative, no PSS deferral |
| * is done. |
| */ |
| private volatile long mPssDeferralTime = 0; |
| |
| /** |
| * Processes we want to collect PSS or RSS data from. |
| */ |
| @GuardedBy("mProfilerLock") |
| private final ArrayList<ProcessProfileRecord> mPendingPssOrRssProfiles = new ArrayList<>(); |
| |
| /** |
| * Depth of overlapping activity-start PSS deferral notes |
| */ |
| private final AtomicInteger mActivityStartingNesting = new AtomicInteger(0); |
| |
| /** |
| * Last time we requested PSS data of all processes. |
| */ |
| @GuardedBy("mProfilerLock") |
| private long mLastFullPssTime = SystemClock.uptimeMillis(); |
| |
| /** |
| * If set, the next time we collect PSS or RSS data we should do a full collection with data |
| * from native processes and the kernel. |
| */ |
| @GuardedBy("mProfilerLock") |
| private boolean mFullPssOrRssPending = false; |
| |
| /** |
| * If true, we are running under a test environment so will sample PSS or RSS from processes |
| * much more rapidly to try to collect better data when the tests are rapidly running through |
| * apps. |
| */ |
| private volatile boolean mTestPssOrRssMode = false; |
| |
| private final LowMemDetector mLowMemDetector; |
| |
| /** |
| * Allow the current computed overall memory level of the system to go down? |
| * This is set to false when we are killing processes for reasons other than |
| * memory management, so that the now smaller process list will not be taken as |
| * an indication that memory is tighter. |
| */ |
| @GuardedBy("mService") |
| private boolean mAllowLowerMemLevel = false; |
| |
| /** |
| * The last computed memory level, for holding when we are in a state that |
| * processes are going away for other reasons. |
| */ |
| @GuardedBy("mService") |
| private @MemFactor int mLastMemoryLevel = ADJ_MEM_FACTOR_NORMAL; |
| |
| @GuardedBy("mService") |
| private @MemFactor int mMemFactorOverride = ADJ_MEM_FACTOR_NOTHING; |
| |
| /** |
| * The last total number of process we have, to determine if changes actually look |
| * like a shrinking number of process due to lower RAM. |
| */ |
| @GuardedBy("mService") |
| private int mLastNumProcesses; |
| |
| /** |
| * Total time spent with RAM that has been added in the past since the last idle time. |
| */ |
| @GuardedBy("mProcLock") |
| private long mLowRamTimeSinceLastIdle = 0; |
| |
| /** |
| * If RAM is currently low, when that horrible situation started. |
| */ |
| @GuardedBy("mProcLock") |
| private long mLowRamStartTime = 0; |
| |
| /** |
| * Last time we report a memory usage. |
| */ |
| @GuardedBy("mService") |
| private long mLastMemUsageReportTime = 0; |
| |
| /** |
| * List of processes that should gc as soon as things are idle. |
| */ |
| @GuardedBy("mProfilerLock") |
| private final ArrayList<ProcessRecord> mProcessesToGc = new ArrayList<>(); |
| |
| /** |
| * Stores a map of process name -> agent string. When a process is started and mAgentAppMap |
| * is not null, this map is checked and the mapped agent installed during bind-time. Note: |
| * A non-null agent in mProfileInfo overrides this. |
| */ |
| @GuardedBy("mProfilerLock") |
| private @Nullable Map<String, String> mAppAgentMap = null; |
| |
| @GuardedBy("mProfilerLock") |
| private int mProfileType = 0; |
| |
| @GuardedBy("mProfilerLock") |
| private final ProfileData mProfileData = new ProfileData(); |
| |
| @GuardedBy("mProfilerLock") |
| private final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>(); |
| |
| @GuardedBy("mProfilerLock") |
| private String mMemWatchDumpProcName; |
| |
| @GuardedBy("mProfilerLock") |
| private Uri mMemWatchDumpUri; |
| |
| @GuardedBy("mProfilerLock") |
| private int mMemWatchDumpPid; |
| |
| @GuardedBy("mProfilerLock") |
| private int mMemWatchDumpUid; |
| |
| @GuardedBy("mProfilerLock") |
| private boolean mMemWatchIsUserInitiated; |
| |
| @GuardedBy("mService") |
| boolean mHasHomeProcess; |
| |
| @GuardedBy("mService") |
| boolean mHasPreviousProcess; |
| |
| /** |
| * Used to collect per-process CPU use for ANRs, battery stats, etc. |
| * Must acquire this object's lock when accessing it. |
| * NOTE: this lock will be held while doing long operations (trawling |
| * through all processes in /proc), so it should never be acquired by |
| * any critical paths such as when holding the main activity manager lock. |
| */ |
| private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker( |
| MONITOR_THREAD_CPU_USAGE); |
| private final AtomicLong mLastCpuTime = new AtomicLong(0); |
| private final AtomicBoolean mProcessCpuMutexFree = new AtomicBoolean(true); |
| private final CountDownLatch mProcessCpuInitLatch = new CountDownLatch(1); |
| |
| private volatile long mLastWriteTime = 0; |
| |
| /** |
| * Runtime CPU use collection thread. This object's lock is used to |
| * perform synchronization with the thread (notifying it to run). |
| */ |
| private final Thread mProcessCpuThread; |
| |
| private final ActivityManagerService mService; |
| private final Handler mBgHandler; |
| |
| final CachedAppsWatermarkData mCachedAppsWatermarkData = new CachedAppsWatermarkData(); |
| |
| /** |
| * The lock to guard some of the profiling data here and {@link ProcessProfileRecord}. |
| * |
| * <p> |
| * The function suffix with this lock would be "-LPf" (Locked with Profiler lock). |
| * </p> |
| */ |
| final Object mProfilerLock = new Object(); |
| |
| final ActivityManagerGlobalLock mProcLock; |
| |
| /** |
| * Observe DeviceConfig changes to the PSS calculation interval |
| */ |
| private final DeviceConfig.OnPropertiesChangedListener mPssDelayConfigListener = |
| new DeviceConfig.OnPropertiesChangedListener() { |
| @Override |
| public void onPropertiesChanged(Properties properties) { |
| if (properties.getKeyset().contains(ACTIVITY_START_PSS_DEFER_CONFIG)) { |
| mPssDeferralTime = properties.getLong(ACTIVITY_START_PSS_DEFER_CONFIG, 0); |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, "Activity-start PSS delay now " |
| + mPssDeferralTime + " ms"); |
| } |
| } |
| } |
| }; |
| |
| private class ProfileData { |
| private String mProfileApp = null; |
| private ProcessRecord mProfileProc = null; |
| private ProfilerInfo mProfilerInfo = null; |
| |
| void setProfileApp(String profileApp) { |
| mProfileApp = profileApp; |
| if (mService.mAtmInternal != null) { |
| mService.mAtmInternal.setProfileApp(profileApp); |
| } |
| } |
| |
| String getProfileApp() { |
| return mProfileApp; |
| } |
| |
| void setProfileProc(ProcessRecord profileProc) { |
| mProfileProc = profileProc; |
| if (mService.mAtmInternal != null) { |
| mService.mAtmInternal.setProfileProc(profileProc == null ? null |
| : profileProc.getWindowProcessController()); |
| } |
| } |
| |
| ProcessRecord getProfileProc() { |
| return mProfileProc; |
| } |
| |
| void setProfilerInfo(ProfilerInfo profilerInfo) { |
| mProfilerInfo = profilerInfo; |
| if (mService.mAtmInternal != null) { |
| mService.mAtmInternal.setProfilerInfo(profilerInfo); |
| } |
| } |
| |
| ProfilerInfo getProfilerInfo() { |
| return mProfilerInfo; |
| } |
| } |
| |
| /** |
| * A simple data class holding the information about the cached apps high watermark. |
| * |
| * Keep it sync with the frameworks/proto_logging/stats/atoms.proto |
| */ |
| class CachedAppsWatermarkData { |
| /** The high water mark of the number of cached apps. */ |
| @GuardedBy("mProcLock") |
| int mCachedAppHighWatermark; |
| |
| /** |
| * The uptime (in seconds) at the high watermark. |
| * Note this is going to be pull metrics, so we'll need the timestamp here. |
| */ |
| @GuardedBy("mProcLock") |
| int mUptimeInSeconds; |
| |
| /** The number of binder proxy at that high water mark. */ |
| @GuardedBy("mProcLock") |
| int mBinderProxySnapshot; |
| |
| /** Free physical memory (in kb) on device. */ |
| @GuardedBy("mProcLock") |
| int mFreeInKb; |
| |
| /** Cched physical memory (in kb) on device. */ |
| @GuardedBy("mProcLock") |
| int mCachedInKb; |
| |
| /** zram (in kb) on device. */ |
| @GuardedBy("mProcLock") |
| int mZramInKb; |
| |
| /** Kernel memory (in kb) on device. */ |
| @GuardedBy("mProcLock") |
| int mKernelInKb; |
| |
| /** The number of apps in frozen state. */ |
| @GuardedBy("mProcLock") |
| int mNumOfFrozenApps; |
| |
| /** The longest frozen time (now - last_frozen) in current frozen apps. */ |
| @GuardedBy("mProcLock") |
| int mLongestFrozenTimeInSeconds; |
| |
| /** The shortest frozen time (now - last_frozen) in current frozen apps. */ |
| @GuardedBy("mProcLock") |
| int mShortestFrozenTimeInSeconds; |
| |
| /** The mean frozen time (now - last_frozen) in current frozen apps. */ |
| @GuardedBy("mProcLock") |
| int mMeanFrozenTimeInSeconds; |
| |
| /** The average frozen time (now - last_frozen) in current frozen apps. */ |
| @GuardedBy("mProcLock") |
| int mAverageFrozenTimeInSeconds; |
| |
| /** |
| * This is an array holding the frozen app durations temporarily |
| * while updating the cached app high watermark. |
| */ |
| @GuardedBy("mProcLock") |
| private long[] mCachedAppFrozenDurations; |
| |
| /** |
| * The earliest frozen timestamp within the frozen apps. |
| */ |
| @GuardedBy("mProcLock") |
| private long mEarliestFrozenTimestamp; |
| |
| /** |
| * The most recent frozen timestamp within the frozen apps. |
| */ |
| @GuardedBy("mProcLock") |
| private long mLatestFrozenTimestamp; |
| |
| /** |
| * The sum of total frozen durations of all frozen apps. |
| */ |
| @GuardedBy("mProcLock") |
| private long mTotalFrozenDurations; |
| |
| @GuardedBy("mProcLock") |
| void updateCachedAppsHighWatermarkIfNecessaryLocked(int numOfCachedApps, long now) { |
| if (numOfCachedApps > mCachedAppHighWatermark) { |
| mCachedAppHighWatermark = numOfCachedApps; |
| mUptimeInSeconds = (int) (now / 1000); |
| |
| // The rest of the updates are pretty costly, do it in a separated handler. |
| mService.mHandler.removeMessages( |
| ActivityManagerService.UPDATE_CACHED_APP_HIGH_WATERMARK); |
| mService.mHandler.obtainMessage( |
| ActivityManagerService.UPDATE_CACHED_APP_HIGH_WATERMARK, Long.valueOf(now)) |
| .sendToTarget(); |
| } |
| } |
| |
| void updateCachedAppsSnapshot(long now) { |
| synchronized (mProcLock) { |
| mEarliestFrozenTimestamp = now; |
| mLatestFrozenTimestamp = 0L; |
| mTotalFrozenDurations = 0L; |
| mNumOfFrozenApps = 0; |
| final int lruSize = mService.mProcessList.getLruSizeLOSP(); |
| if (mCachedAppFrozenDurations == null |
| || mCachedAppFrozenDurations.length < lruSize) { |
| mCachedAppFrozenDurations = new long[Math.max( |
| lruSize, mService.mConstants.CUR_MAX_CACHED_PROCESSES)]; |
| } |
| mService.mProcessList.forEachLruProcessesLOSP(true, app -> { |
| if (app.mOptRecord.isFrozen()) { |
| final long freezeTime = app.mOptRecord.getFreezeUnfreezeTime(); |
| if (freezeTime < mEarliestFrozenTimestamp) { |
| mEarliestFrozenTimestamp = freezeTime; |
| } |
| if (freezeTime > mLatestFrozenTimestamp) { |
| mLatestFrozenTimestamp = freezeTime; |
| } |
| final long duration = now - freezeTime; |
| mTotalFrozenDurations += duration; |
| mCachedAppFrozenDurations[mNumOfFrozenApps++] = duration; |
| } |
| }); |
| if (mNumOfFrozenApps > 0) { |
| mLongestFrozenTimeInSeconds = (int) ((now - mEarliestFrozenTimestamp) / 1000); |
| mShortestFrozenTimeInSeconds = (int) ((now - mLatestFrozenTimestamp) / 1000); |
| mAverageFrozenTimeInSeconds = |
| (int) ((mTotalFrozenDurations / mNumOfFrozenApps) / 1000); |
| mMeanFrozenTimeInSeconds = (int) (QuickSelect.select(mCachedAppFrozenDurations, |
| 0, mNumOfFrozenApps, mNumOfFrozenApps / 2) / 1000); |
| } |
| |
| mBinderProxySnapshot = 0; |
| final SparseIntArray counts = BinderInternal.nGetBinderProxyPerUidCounts(); |
| if (counts != null) { |
| for (int i = 0, size = counts.size(); i < size; i++) { |
| final int uid = counts.keyAt(i); |
| final UidRecord uidRec = mService.mProcessList.getUidRecordLOSP(uid); |
| if (uidRec != null) { |
| mBinderProxySnapshot += counts.valueAt(i); |
| } |
| } |
| } |
| |
| final MemInfoReader memInfo = new MemInfoReader(); |
| memInfo.readMemInfo(); |
| mFreeInKb = (int) memInfo.getFreeSizeKb(); |
| mCachedInKb = (int) memInfo.getCachedSizeKb(); |
| mZramInKb = (int) memInfo.getZramTotalSizeKb(); |
| mKernelInKb = (int) memInfo.getKernelUsedSizeKb(); |
| } |
| } |
| |
| @NonNull |
| StatsEvent getCachedAppsHighWatermarkStats(int atomTag, boolean resetAfterPull) { |
| synchronized (mProcLock) { |
| final StatsEvent event = FrameworkStatsLog.buildStatsEvent(atomTag, |
| mCachedAppHighWatermark, |
| mUptimeInSeconds, |
| mBinderProxySnapshot, |
| mFreeInKb, |
| mCachedInKb, |
| mZramInKb, |
| mKernelInKb, |
| mNumOfFrozenApps, |
| mLongestFrozenTimeInSeconds, |
| mShortestFrozenTimeInSeconds, |
| mMeanFrozenTimeInSeconds, |
| mAverageFrozenTimeInSeconds); |
| if (resetAfterPull) { |
| mCachedAppHighWatermark = 0; |
| mUptimeInSeconds = 0; |
| mBinderProxySnapshot = 0; |
| mFreeInKb = 0; |
| mCachedInKb = 0; |
| mZramInKb = 0; |
| mKernelInKb = 0; |
| mNumOfFrozenApps = 0; |
| mLongestFrozenTimeInSeconds = 0; |
| mShortestFrozenTimeInSeconds = 0; |
| mMeanFrozenTimeInSeconds = 0; |
| mAverageFrozenTimeInSeconds = 0; |
| } |
| return event; |
| } |
| } |
| } |
| |
| private class BgHandler extends Handler { |
| static final int COLLECT_PSS_BG_MSG = 1; |
| static final int DEFER_PSS_MSG = 2; |
| static final int STOP_DEFERRING_PSS_MSG = 3; |
| static final int MEMORY_PRESSURE_CHANGED = 4; |
| BgHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case COLLECT_PSS_BG_MSG: |
| if (!Flags.removeAppProfilerPssCollection()) { |
| collectPssInBackground(); |
| } else { |
| collectRssInBackground(); |
| } |
| break; |
| case DEFER_PSS_MSG: |
| deferPssForActivityStart(); |
| break; |
| case STOP_DEFERRING_PSS_MSG: |
| stopDeferPss(); |
| break; |
| case MEMORY_PRESSURE_CHANGED: |
| synchronized (mService) { |
| handleMemoryPressureChangedLocked(msg.arg1, msg.arg2); |
| } |
| break; |
| } |
| } |
| } |
| |
| private void collectPssInBackground() { |
| long start = SystemClock.uptimeMillis(); |
| MemInfoReader memInfo = null; |
| synchronized (mProfilerLock) { |
| if (mFullPssOrRssPending) { |
| mFullPssOrRssPending = false; |
| memInfo = new MemInfoReader(); |
| } |
| } |
| if (memInfo != null) { |
| updateCpuStatsNow(); |
| long nativeTotalPss = 0; |
| final List<ProcessCpuTracker.Stats> stats; |
| synchronized (mProcessCpuTracker) { |
| stats = mProcessCpuTracker.getStats(st -> { |
| return st.vsize > 0 && st.uid < FIRST_APPLICATION_UID; |
| }); |
| } |
| |
| if (!mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED) { |
| final int numOfStats = stats.size(); |
| for (int j = 0; j < numOfStats; j++) { |
| synchronized (mService.mPidsSelfLocked) { |
| if (mService.mPidsSelfLocked.indexOfKey(stats.get(j).pid) >= 0) { |
| // This is one of our own processes; skip it. |
| continue; |
| } |
| } |
| nativeTotalPss += Debug.getPss(stats.get(j).pid, null, null); |
| } |
| } |
| |
| memInfo.readMemInfo(); |
| synchronized (mService.mProcessStats.mLock) { |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, "Collected native and kernel memory in " |
| + (SystemClock.uptimeMillis() - start) + "ms"); |
| } |
| final long cachedKb = memInfo.getCachedSizeKb(); |
| final long freeKb = memInfo.getFreeSizeKb(); |
| final long zramKb = memInfo.getZramTotalSizeKb(); |
| final long kernelKb = memInfo.getKernelUsedSizeKb(); |
| EventLogTags.writeAmMeminfo(cachedKb * 1024, freeKb * 1024, zramKb * 1024, |
| kernelKb * 1024, nativeTotalPss * 1024); |
| mService.mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, |
| nativeTotalPss); |
| } |
| } |
| |
| int num = 0; |
| long[] tmp = new long[3]; |
| do { |
| ProcessProfileRecord profile; |
| int procState; |
| int statType; |
| int pid = -1; |
| long lastPssTime; |
| synchronized (mProfilerLock) { |
| if (mPendingPssOrRssProfiles.size() <= 0) { |
| if (mTestPssOrRssMode || DEBUG_PSS) { |
| Slog.d(TAG_PSS, |
| "Collected pss of " + num + " processes in " |
| + (SystemClock.uptimeMillis() - start) + "ms"); |
| } |
| mPendingPssOrRssProfiles.clear(); |
| return; |
| } |
| profile = mPendingPssOrRssProfiles.remove(0); |
| procState = profile.getPssProcState(); |
| statType = profile.getPssStatType(); |
| lastPssTime = profile.getLastPssTime(); |
| long now = SystemClock.uptimeMillis(); |
| if (profile.getThread() != null && procState == profile.getSetProcState() |
| && (lastPssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) < now) { |
| pid = profile.getPid(); |
| } else { |
| profile.abortNextPssTime(); |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, "Skipped pss collection of " + pid |
| + ": still need " |
| + (lastPssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE - now) |
| + "ms until safe"); |
| } |
| profile = null; |
| pid = 0; |
| } |
| } |
| if (profile != null) { |
| long startTime = SystemClock.currentThreadTimeMillis(); |
| // skip background PSS calculation under the following situations: |
| // - app is capturing camera imagery |
| // - app is frozen and we have already collected PSS once. |
| final boolean skipPSSCollection = |
| (profile.mApp.mOptRecord != null |
| && profile.mApp.mOptRecord.skipPSSCollectionBecauseFrozen()) |
| || mService.isCameraActiveForUid(profile.mApp.uid) |
| || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED; |
| long pss = skipPSSCollection ? 0 : Debug.getPss(pid, tmp, null); |
| long endTime = SystemClock.currentThreadTimeMillis(); |
| synchronized (mProfilerLock) { |
| if (pss != 0 && profile.getThread() != null |
| && profile.getSetProcState() == procState |
| && profile.getPid() == pid && profile.getLastPssTime() == lastPssTime) { |
| num++; |
| profile.commitNextPssTime(); |
| recordPssSampleLPf(profile, procState, pss, tmp[0], tmp[1], tmp[2], |
| statType, endTime - startTime, SystemClock.uptimeMillis()); |
| } else { |
| profile.abortNextPssTime(); |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, "Skipped pss collection of " + pid |
| + ": " + (profile.getThread() == null ? "NO_THREAD " : "") |
| + (skipPSSCollection ? "SKIP_PSS_COLLECTION " : "") |
| + (profile.getPid() != pid ? "PID_CHANGED " : "") |
| + " initState=" + procState + " curState=" |
| + profile.getSetProcState() + " " |
| + (profile.getLastPssTime() != lastPssTime |
| ? "TIME_CHANGED" : "")); |
| } |
| } |
| } |
| } |
| } while (true); |
| } |
| |
| // This method is analogous to collectPssInBackground() and is intended to be used as a |
| // replacement if Flags.removeAppProfilerPssCollection() is enabled. References to PSS in |
| // methods outside of AppProfiler have generally been kept where a new RSS equivalent is not |
| // technically necessary. These can be updated once the flag is completely rolled out. |
| private void collectRssInBackground() { |
| long start = SystemClock.uptimeMillis(); |
| MemInfoReader memInfo = null; |
| synchronized (mProfilerLock) { |
| if (mFullPssOrRssPending) { |
| mFullPssOrRssPending = false; |
| memInfo = new MemInfoReader(); |
| } |
| } |
| if (memInfo != null) { |
| updateCpuStatsNow(); |
| long nativeTotalRss = 0; |
| final List<ProcessCpuTracker.Stats> stats; |
| synchronized (mProcessCpuTracker) { |
| stats = mProcessCpuTracker.getStats(st -> { |
| return st.vsize > 0 && st.uid < FIRST_APPLICATION_UID; |
| }); |
| } |
| |
| // We assume that if PSS collection isn't needed or desired, RSS collection can be |
| // disabled as well. |
| if (!mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED) { |
| final int numOfStats = stats.size(); |
| for (int j = 0; j < numOfStats; j++) { |
| synchronized (mService.mPidsSelfLocked) { |
| if (mService.mPidsSelfLocked.indexOfKey(stats.get(j).pid) >= 0) { |
| // This is one of our own processes; skip it. |
| continue; |
| } |
| } |
| nativeTotalRss += Debug.getRss(stats.get(j).pid, null); |
| } |
| } |
| |
| memInfo.readMemInfo(); |
| synchronized (mService.mProcessStats.mLock) { |
| // We assume that an enabled DEBUG_PSS can apply to RSS as well, since only one of |
| // either collectPssInBackground() or collectRssInBackground() will be used. |
| if (DEBUG_RSS) { |
| Slog.d(TAG_RSS, "Collected native and kernel memory in " |
| + (SystemClock.uptimeMillis() - start) + "ms"); |
| } |
| final long cachedKb = memInfo.getCachedSizeKb(); |
| final long freeKb = memInfo.getFreeSizeKb(); |
| final long zramKb = memInfo.getZramTotalSizeKb(); |
| final long kernelKb = memInfo.getKernelUsedSizeKb(); |
| // The last value needs to be updated in log tags to refer to RSS; this will be |
| // updated once the flag is fully rolled out. |
| EventLogTags.writeAmMeminfo(cachedKb * 1024, freeKb * 1024, zramKb * 1024, |
| kernelKb * 1024, nativeTotalRss * 1024); |
| mService.mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, |
| nativeTotalRss); |
| } |
| } |
| |
| // This loop differs from its original form in collectPssInBackground(), as it does not |
| // collect USS or SwapPss (since those are reported in smaps, not status). |
| int num = 0; |
| do { |
| ProcessProfileRecord profile; |
| int procState; |
| int statType; |
| int pid = -1; |
| long lastRssTime; |
| synchronized (mProfilerLock) { |
| if (mPendingPssOrRssProfiles.size() <= 0) { |
| if (mTestPssOrRssMode || DEBUG_RSS) { |
| Slog.d(TAG_RSS, |
| "Collected rss of " + num + " processes in " |
| + (SystemClock.uptimeMillis() - start) + "ms"); |
| } |
| mPendingPssOrRssProfiles.clear(); |
| return; |
| } |
| profile = mPendingPssOrRssProfiles.remove(0); |
| procState = profile.getPssProcState(); |
| statType = profile.getPssStatType(); |
| lastRssTime = profile.getLastPssTime(); |
| long now = SystemClock.uptimeMillis(); |
| if (profile.getThread() != null && procState == profile.getSetProcState() |
| && (lastRssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) < now) { |
| pid = profile.getPid(); |
| } else { |
| profile.abortNextPssTime(); |
| if (DEBUG_RSS) { |
| Slog.d(TAG_RSS, "Skipped rss collection of " + pid |
| + ": still need " |
| + (lastRssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE - now) |
| + "ms until safe"); |
| } |
| profile = null; |
| pid = 0; |
| } |
| } |
| if (profile != null) { |
| long startTime = SystemClock.currentThreadTimeMillis(); |
| // skip background RSS calculation under the following situations: |
| // - app is capturing camera imagery |
| // - app is frozen and we have already collected RSS once. |
| final boolean skipRSSCollection = |
| (profile.mApp.mOptRecord != null |
| && profile.mApp.mOptRecord.skipPSSCollectionBecauseFrozen()) |
| || mService.isCameraActiveForUid(profile.mApp.uid) |
| || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED; |
| long rss = skipRSSCollection ? 0 : Debug.getRss(pid, null); |
| long endTime = SystemClock.currentThreadTimeMillis(); |
| synchronized (mProfilerLock) { |
| if (rss != 0 && profile.getThread() != null |
| && profile.getSetProcState() == procState |
| && profile.getPid() == pid && profile.getLastPssTime() == lastRssTime) { |
| num++; |
| profile.commitNextPssTime(); |
| recordRssSampleLPf(profile, procState, rss, statType, endTime - startTime, |
| SystemClock.uptimeMillis()); |
| } else { |
| profile.abortNextPssTime(); |
| if (DEBUG_RSS) { |
| Slog.d(TAG_RSS, "Skipped rss collection of " + pid |
| + ": " + (profile.getThread() == null ? "NO_THREAD " : "") |
| + (skipRSSCollection ? "SKIP_RSS_COLLECTION " : "") |
| + (profile.getPid() != pid ? "PID_CHANGED " : "") |
| + " initState=" + procState + " curState=" |
| + profile.getSetProcState() + " " |
| + (profile.getLastPssTime() != lastRssTime |
| ? "TIME_CHANGED" : "")); |
| } |
| } |
| } |
| } |
| } while (true); |
| } |
| |
| @GuardedBy("mProfilerLock") |
| void updateNextPssTimeLPf(int procState, ProcessProfileRecord profile, long now, |
| boolean forceUpdate) { |
| if (!forceUpdate) { |
| if (now <= profile.getNextPssTime() && now <= Math.max(profile.getLastPssTime() |
| + ProcessList.PSS_MAX_INTERVAL, profile.getLastStateTime() |
| + ProcessList.minTimeFromStateChange(mTestPssOrRssMode))) { |
| // update is not due, ignore it. |
| return; |
| } |
| if (!requestPssLPf(profile, procState)) { |
| return; |
| } |
| } |
| profile.setNextPssTime(profile.computeNextPssTime(procState, |
| mTestPssOrRssMode, mService.mAtmInternal.isSleeping(), now)); |
| } |
| |
| /** |
| * Record new PSS sample for a process. |
| */ |
| @GuardedBy("mProfilerLock") |
| private void recordPssSampleLPf(ProcessProfileRecord profile, int procState, long pss, long uss, |
| long swapPss, long rss, int statType, long pssDuration, long now) { |
| final ProcessRecord proc = profile.mApp; |
| EventLogTags.writeAmPss( |
| profile.getPid(), proc.uid, proc.processName, pss * 1024, uss * 1024, |
| swapPss * 1024, rss * 1024, statType, procState, pssDuration); |
| profile.setLastPssTime(now); |
| profile.addPss(pss, uss, rss, true, statType, pssDuration); |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, |
| "pss of " + proc.toShortString() + ": " + pss |
| + " lastPss=" + profile.getLastPss() |
| + " state=" + ProcessList.makeProcStateString(procState)); |
| } |
| if (profile.getInitialIdlePssOrRss() == 0) { |
| profile.setInitialIdlePssOrRss(pss); |
| } |
| profile.setLastPss(pss); |
| profile.setLastSwapPss(swapPss); |
| if (procState >= ActivityManager.PROCESS_STATE_HOME) { |
| profile.setLastCachedPss(pss); |
| profile.setLastCachedSwapPss(swapPss); |
| } |
| profile.setLastRss(rss); |
| |
| final SparseArray<Pair<Long, String>> watchUids = |
| mMemWatchProcesses.getMap().get(proc.processName); |
| Long check = null; |
| if (watchUids != null) { |
| Pair<Long, String> val = watchUids.get(proc.uid); |
| if (val == null) { |
| val = watchUids.get(0); |
| } |
| if (val != null) { |
| check = val.first; |
| } |
| } |
| if (check != null) { |
| if ((pss * 1024) >= check && profile.getThread() != null |
| && mMemWatchDumpProcName == null) { |
| if (Build.IS_DEBUGGABLE || proc.isDebuggable()) { |
| Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting"); |
| startHeapDumpLPf(profile, false); |
| } else { |
| Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check |
| + ", but debugging not enabled"); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Record new RSS sample for a process. |
| * |
| * This method is analogous to recordPssSampleLPf() and is intended to be used as a replacement |
| * if Flags.removeAppProfilerPssCollection() is enabled. Functionally, this differs in that PSS, |
| * SwapPss, and USS are no longer collected and reported. |
| * |
| * This method will also poll PSS if the app has requested that a heap dump be taken if its PSS |
| * reaches some threshold set with ActivityManager.setWatchHeapLimit(). |
| */ |
| @GuardedBy("mProfilerLock") |
| private void recordRssSampleLPf(ProcessProfileRecord profile, int procState, long rss, |
| int statType, long rssDuration, long now) { |
| final ProcessRecord proc = profile.mApp; |
| // TODO(b/296454553): writeAmPss needs to be renamed to writeAmRss, and the zeroed out |
| // fields need to be removed. This will be updated once the flag is fully rolled out to |
| // avoid churn in the .logtags file, which has a mapping of IDs to tags (and is also |
| // technically deprecated). |
| EventLogTags.writeAmPss( |
| profile.getPid(), proc.uid, proc.processName, /* pss = */ 0, /* uss = */ 0, |
| /* swapPss = */ 0, rss * 1024, statType, procState, rssDuration); |
| profile.setLastPssTime(now); |
| // The PSS here is emitted in logs, so we can zero it out instead of subbing in RSS. |
| profile.addPss(/* pss = */ 0, /* uss = */ 0, rss, true, statType, rssDuration); |
| if (DEBUG_RSS) { |
| Slog.d(TAG_RSS, |
| "rss of " + proc.toShortString() + ": " + rss |
| + " lastRss=" + profile.getLastRss() |
| + " state=" + ProcessList.makeProcStateString(procState)); |
| } |
| if (profile.getInitialIdlePssOrRss() == 0) { |
| profile.setInitialIdlePssOrRss(rss); |
| } |
| profile.setLastRss(rss); |
| if (procState >= ActivityManager.PROCESS_STATE_HOME) { |
| profile.setLastCachedRss(rss); |
| } |
| |
| final SparseArray<Pair<Long, String>> watchUids = |
| mMemWatchProcesses.getMap().get(proc.processName); |
| Long check = null; |
| if (watchUids != null) { |
| Pair<Long, String> val = watchUids.get(proc.uid); |
| if (val == null) { |
| val = watchUids.get(0); |
| } |
| if (val != null) { |
| check = val.first; |
| } |
| } |
| |
| if (check != null) { |
| long pss = Debug.getPss(profile.getPid(), null, null); |
| if ((pss * 1024) >= check && profile.getThread() != null |
| && mMemWatchDumpProcName == null) { |
| if (Build.IS_DEBUGGABLE || proc.isDebuggable()) { |
| Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting"); |
| startHeapDumpLPf(profile, false); |
| } else { |
| Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check |
| + ", but debugging not enabled"); |
| } |
| } |
| } |
| } |
| |
| private final class RecordPssRunnable implements Runnable { |
| private final ProcessProfileRecord mProfile; |
| private final Uri mDumpUri; |
| private final ContentResolver mContentResolver; |
| |
| RecordPssRunnable(ProcessProfileRecord profile, Uri dumpUri, |
| ContentResolver contentResolver) { |
| mProfile = profile; |
| mDumpUri = dumpUri; |
| mContentResolver = contentResolver; |
| } |
| |
| @Override |
| public void run() { |
| try (ParcelFileDescriptor fd = mContentResolver.openFileDescriptor(mDumpUri, "rw")) { |
| IApplicationThread thread = mProfile.getThread(); |
| if (thread != null) { |
| try { |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, "Requesting dump heap from " |
| + mProfile.mApp + " to " + mDumpUri.getPath()); |
| } |
| thread.dumpHeap(/* managed= */ true, |
| /* mallocInfo= */ false, |
| /* runGc= */ false, |
| /* dumpbitmaps= */ null, |
| mDumpUri.getPath(), fd, |
| /* finishCallback= */ null); |
| } catch (RemoteException e) { |
| } |
| } |
| } catch (IOException e) { |
| Slog.e(TAG, "Failed to dump heap", e); |
| // Need to clear the heap dump variables, otherwise no further heap dumps will be |
| // attempted. |
| abortHeapDump(mProfile.mApp.processName); |
| } |
| } |
| } |
| |
| @GuardedBy("mProfilerLock") |
| void startHeapDumpLPf(ProcessProfileRecord profile, boolean isUserInitiated) { |
| final ProcessRecord proc = profile.mApp; |
| mMemWatchDumpProcName = proc.processName; |
| mMemWatchDumpUri = makeHeapDumpUri(proc.processName); |
| mMemWatchDumpPid = profile.getPid(); |
| mMemWatchDumpUid = proc.uid; |
| mMemWatchIsUserInitiated = isUserInitiated; |
| Context ctx; |
| try { |
| ctx = mService.mContext.createPackageContextAsUser("android", 0, |
| UserHandle.getUserHandleForUid(mMemWatchDumpUid)); |
| } catch (NameNotFoundException e) { |
| throw new RuntimeException("android package not found."); |
| } |
| BackgroundThread.getHandler().post( |
| new RecordPssRunnable(profile, mMemWatchDumpUri, ctx.getContentResolver())); |
| } |
| |
| void dumpHeapFinished(String path, int callerPid) { |
| synchronized (mProfilerLock) { |
| if (callerPid != mMemWatchDumpPid) { |
| Slog.w(TAG, "dumpHeapFinished: Calling pid " + Binder.getCallingPid() |
| + " does not match last pid " + mMemWatchDumpPid); |
| return; |
| } |
| if (mMemWatchDumpUri == null || !mMemWatchDumpUri.getPath().equals(path)) { |
| Slog.w(TAG, "dumpHeapFinished: Calling path " + path |
| + " does not match last path " + mMemWatchDumpUri); |
| return; |
| } |
| if (DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path); |
| mService.mHandler.sendEmptyMessage( |
| ActivityManagerService.POST_DUMP_HEAP_NOTIFICATION_MSG); |
| |
| // Forced gc to clean up the remnant hprof fd. |
| Runtime.getRuntime().gc(); |
| } |
| } |
| |
| void handlePostDumpHeapNotification() { |
| final String procName; |
| final int uid; |
| final long memLimit; |
| final String reportPackage; |
| final boolean isUserInitiated; |
| synchronized (mProfilerLock) { |
| uid = mMemWatchDumpUid; |
| procName = mMemWatchDumpProcName; |
| Pair<Long, String> val = mMemWatchProcesses.get(procName, uid); |
| if (val == null) { |
| val = mMemWatchProcesses.get(procName, 0); |
| } |
| if (val != null) { |
| memLimit = val.first; |
| reportPackage = val.second; |
| } else { |
| memLimit = 0; |
| reportPackage = null; |
| } |
| isUserInitiated = mMemWatchIsUserInitiated; |
| |
| mMemWatchDumpUri = null; |
| mMemWatchDumpProcName = null; |
| mMemWatchDumpPid = -1; |
| mMemWatchDumpUid = -1; |
| } |
| if (procName == null) { |
| return; |
| } |
| |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, "Showing dump heap notification from " + procName + "/" + uid); |
| } |
| |
| Intent dumpFinishedIntent = new Intent(ACTION_HEAP_DUMP_FINISHED); |
| // Send this only to the Shell package. |
| dumpFinishedIntent.setPackage("com.android.shell"); |
| dumpFinishedIntent.putExtra(Intent.EXTRA_UID, uid); |
| dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_IS_USER_INITIATED, isUserInitiated); |
| dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_SIZE_BYTES, memLimit); |
| dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_REPORT_PACKAGE, reportPackage); |
| dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_PROCESS_NAME, procName); |
| |
| mService.mContext.sendBroadcastAsUser(dumpFinishedIntent, |
| UserHandle.getUserHandleForUid(uid)); |
| } |
| |
| void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize, |
| String reportPackage) { |
| synchronized (mProfilerLock) { |
| if (maxMemSize > 0) { |
| mMemWatchProcesses.put(processName, uid, new Pair(maxMemSize, reportPackage)); |
| } else { |
| if (uid != 0) { |
| mMemWatchProcesses.remove(processName, uid); |
| } else { |
| mMemWatchProcesses.getMap().remove(processName); |
| } |
| } |
| } |
| } |
| |
| /** Clear the currently executing heap dump variables so a new heap dump can be started. */ |
| private void abortHeapDump(String procName) { |
| Message msg = mService.mHandler.obtainMessage(ActivityManagerService.ABORT_DUMPHEAP_MSG); |
| msg.obj = procName; |
| mService.mHandler.sendMessage(msg); |
| } |
| |
| void handleAbortDumpHeap(String procName) { |
| if (procName != null) { |
| synchronized (mProfilerLock) { |
| if (procName.equals(mMemWatchDumpProcName)) { |
| mMemWatchDumpProcName = null; |
| mMemWatchDumpUri = null; |
| mMemWatchDumpPid = -1; |
| mMemWatchDumpUid = -1; |
| } |
| } |
| } |
| } |
| |
| /** @hide */ |
| private static Uri makeHeapDumpUri(String procName) { |
| return Uri.parse("content://com.android.shell.heapdump/" + procName + "_javaheap.bin"); |
| } |
| |
| /** |
| * Schedule PSS collection of a process. |
| */ |
| @GuardedBy("mProfilerLock") |
| private boolean requestPssLPf(ProcessProfileRecord profile, int procState) { |
| if (mPendingPssOrRssProfiles.contains(profile)) { |
| return false; |
| } |
| if (mPendingPssOrRssProfiles.size() == 0) { |
| final long deferral = (mPssDeferralTime > 0 && mActivityStartingNesting.get() > 0) |
| ? mPssDeferralTime : 0; |
| if (DEBUG_PSS && deferral > 0) { |
| Slog.d(TAG_PSS, "requestPssLPf() deferring PSS request by " |
| + deferral + " ms"); |
| } |
| mBgHandler.sendEmptyMessageDelayed(BgHandler.COLLECT_PSS_BG_MSG, deferral); |
| } |
| if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of: " + profile.mApp); |
| profile.setPssProcState(procState); |
| profile.setPssStatType(ProcessStats.ADD_PSS_INTERNAL_SINGLE); |
| mPendingPssOrRssProfiles.add(profile); |
| return true; |
| } |
| |
| /** |
| * Re-defer a posted PSS collection pass, if one exists. Assumes deferral is |
| * currently active policy when called. |
| */ |
| @GuardedBy("mProfilerLock") |
| private void deferPssIfNeededLPf() { |
| if (mPendingPssOrRssProfiles.size() > 0) { |
| mBgHandler.removeMessages(BgHandler.COLLECT_PSS_BG_MSG); |
| mBgHandler.sendEmptyMessageDelayed(BgHandler.COLLECT_PSS_BG_MSG, mPssDeferralTime); |
| } |
| } |
| |
| private void deferPssForActivityStart() { |
| if (mPssDeferralTime > 0) { |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, "Deferring PSS collection for activity start"); |
| } |
| synchronized (mProfilerLock) { |
| deferPssIfNeededLPf(); |
| } |
| mActivityStartingNesting.getAndIncrement(); |
| mBgHandler.sendEmptyMessageDelayed(BgHandler.STOP_DEFERRING_PSS_MSG, mPssDeferralTime); |
| } |
| } |
| |
| private void stopDeferPss() { |
| final int nesting = mActivityStartingNesting.decrementAndGet(); |
| if (nesting <= 0) { |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, "PSS activity start deferral interval ended; now " |
| + nesting); |
| } |
| if (nesting < 0) { |
| Slog.wtf(TAG, "Activity start nesting undercount!"); |
| mActivityStartingNesting.incrementAndGet(); |
| } |
| } else { |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, "Still deferring PSS, nesting=" + nesting); |
| } |
| } |
| } |
| |
| /** |
| * Schedule PSS collection of all processes. |
| */ |
| @GuardedBy("mProcLock") |
| void requestPssAllProcsLPr(long now, boolean always, boolean memLowered) { |
| synchronized (mProfilerLock) { |
| if (!always) { |
| if (now < (mLastFullPssTime |
| + (memLowered ? mService.mConstants.FULL_PSS_LOWERED_INTERVAL |
| : mService.mConstants.FULL_PSS_MIN_INTERVAL))) { |
| return; |
| } |
| } |
| if (DEBUG_PSS) { |
| Slog.d(TAG_PSS, "Requesting pss of all procs! memLowered=" + memLowered); |
| } |
| mLastFullPssTime = now; |
| mFullPssOrRssPending = true; |
| for (int i = mPendingPssOrRssProfiles.size() - 1; i >= 0; i--) { |
| mPendingPssOrRssProfiles.get(i).abortNextPssTime(); |
| } |
| mPendingPssOrRssProfiles.ensureCapacity(mService.mProcessList.getLruSizeLOSP()); |
| mPendingPssOrRssProfiles.clear(); |
| mService.mProcessList.forEachLruProcessesLOSP(false, app -> { |
| final ProcessProfileRecord profile = app.mProfile; |
| if (profile.getThread() == null |
| || profile.getSetProcState() == PROCESS_STATE_NONEXISTENT) { |
| return; |
| } |
| final long lastStateTime = profile.getLastStateTime(); |
| if (memLowered || (always |
| && now > lastStateTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) |
| || now > (lastStateTime + ProcessList.PSS_ALL_INTERVAL)) { |
| profile.setPssProcState(profile.getSetProcState()); |
| profile.setPssStatType(always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL |
| : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM); |
| updateNextPssTimeLPf(profile.getSetProcState(), profile, now, true); |
| mPendingPssOrRssProfiles.add(profile); |
| } |
| }); |
| if (!mBgHandler.hasMessages(BgHandler.COLLECT_PSS_BG_MSG)) { |
| mBgHandler.sendEmptyMessage(BgHandler.COLLECT_PSS_BG_MSG); |
| } |
| } |
| } |
| |
| void setTestPssMode(boolean enabled) { |
| synchronized (mProcLock) { |
| mTestPssOrRssMode = enabled; |
| if (enabled) { |
| // Whenever we enable the mode, we want to take a snapshot all of current |
| // process mem use. |
| requestPssAllProcsLPr(SystemClock.uptimeMillis(), true, true); |
| } |
| } |
| } |
| |
| boolean getTestPssMode() { |
| return mTestPssOrRssMode; |
| } |
| |
| @GuardedBy("mService") |
| @MemFactor int getLastMemoryLevelLocked() { |
| if (mMemFactorOverride != ADJ_MEM_FACTOR_NOTHING) { |
| return mMemFactorOverride; |
| } |
| return mLastMemoryLevel; |
| } |
| |
| @GuardedBy("mService") |
| boolean isLastMemoryLevelNormal() { |
| if (mMemFactorOverride != ADJ_MEM_FACTOR_NOTHING) { |
| return mMemFactorOverride <= ADJ_MEM_FACTOR_NORMAL; |
| } |
| return mLastMemoryLevel <= ADJ_MEM_FACTOR_NORMAL; |
| } |
| |
| @GuardedBy("mProcLock") |
| void updateLowRamTimestampLPr(long now) { |
| mLowRamTimeSinceLastIdle = 0; |
| if (mLowRamStartTime != 0) { |
| mLowRamStartTime = now; |
| } |
| } |
| |
| @GuardedBy("mService") |
| void setAllowLowerMemLevelLocked(boolean allowLowerMemLevel) { |
| mAllowLowerMemLevel = allowLowerMemLevel; |
| } |
| |
| @GuardedBy("mService") |
| void setMemFactorOverrideLocked(@MemFactor int factor) { |
| mMemFactorOverride = factor; |
| } |
| |
| @GuardedBy({"mService", "mProcLock"}) |
| boolean updateLowMemStateLSP(int numCached, int numEmpty, int numTrimming, long now) { |
| int memFactor; |
| if (mLowMemDetector != null && mLowMemDetector.isAvailable()) { |
| memFactor = mLowMemDetector.getMemFactor(); |
| } else { |
| // Now determine the memory trimming level of background processes. |
| // Unfortunately we need to start at the back of the list to do this |
| // properly. We only do this if the number of background apps we |
| // are managing to keep around is less than half the maximum we desire; |
| // if we are keeping a good number around, we'll let them use whatever |
| // memory they want. |
| if (numCached <= mService.mConstants.CUR_TRIM_CACHED_PROCESSES |
| && numEmpty <= mService.mConstants.CUR_TRIM_EMPTY_PROCESSES) { |
| final int numCachedAndEmpty = numCached + numEmpty; |
| if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) { |
| memFactor = ADJ_MEM_FACTOR_CRITICAL; |
| } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { |
| memFactor = ADJ_MEM_FACTOR_LOW; |
| } else { |
| memFactor = ADJ_MEM_FACTOR_MODERATE; |
| } |
| } else { |
| memFactor = ADJ_MEM_FACTOR_NORMAL; |
| } |
| } |
| // We always allow the memory level to go up (better). We only allow it to go |
| // down if we are in a state where that is allowed, *and* the total number of processes |
| // has gone down since last time. |
| if (DEBUG_OOM_ADJ) { |
| Slog.d(TAG_OOM_ADJ, "oom: memFactor=" + memFactor + " override=" + mMemFactorOverride |
| + " last=" + mLastMemoryLevel + " allowLow=" + mAllowLowerMemLevel |
| + " numProcs=" + mService.mProcessList.getLruSizeLOSP() |
| + " last=" + mLastNumProcesses); |
| } |
| boolean override; |
| if (override = (mMemFactorOverride != ADJ_MEM_FACTOR_NOTHING)) { |
| memFactor = mMemFactorOverride; |
| } |
| if (memFactor > mLastMemoryLevel) { |
| if (!override && (!mAllowLowerMemLevel |
| || mService.mProcessList.getLruSizeLOSP() >= mLastNumProcesses)) { |
| memFactor = mLastMemoryLevel; |
| if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "Keeping last mem factor!"); |
| } |
| } |
| if (memFactor != mLastMemoryLevel) { |
| EventLogTags.writeAmMemFactor(memFactor, mLastMemoryLevel); |
| FrameworkStatsLog.write(FrameworkStatsLog.MEMORY_FACTOR_STATE_CHANGED, memFactor); |
| mBgHandler.obtainMessage(BgHandler.MEMORY_PRESSURE_CHANGED, mLastMemoryLevel, memFactor) |
| .sendToTarget(); |
| } |
| |
| mCachedAppsWatermarkData.updateCachedAppsHighWatermarkIfNecessaryLocked( |
| numCached + numEmpty, now); |
| boolean allChanged; |
| int trackerMemFactor; |
| synchronized (mService.mProcessStats.mLock) { |
| allChanged = mService.mProcessStats.setMemFactorLocked(memFactor, |
| mService.mAtmInternal == null || !mService.mAtmInternal.isSleeping(), |
| SystemClock.uptimeMillis() /* re-acquire the time within the lock */); |
| trackerMemFactor = mService.mProcessStats.getMemFactorLocked(); |
| } |
| |
| mLastMemoryLevel = memFactor; |
| mLastNumProcesses = mService.mProcessList.getLruSizeLOSP(); |
| if (mService.mConstants.USE_MODERN_TRIM) { |
| // Modern trim is not sent based on lowmem state |
| // Dispatch UI_HIDDEN to processes that need it |
| mService.mProcessList.forEachLruProcessesLOSP(true, app -> { |
| final ProcessProfileRecord profile = app.mProfile; |
| final IApplicationThread thread; |
| final ProcessStateRecord state = app.mState; |
| if (state.hasProcStateChanged()) { |
| state.setProcStateChanged(false); |
| } |
| int procState = app.mState.getCurProcState(); |
| if (((procState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND |
| && procState < ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) |
| || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) { |
| // If this application is now in the background and it |
| // had done UI, then give it the special trim level to |
| // have it free UI resources. |
| if ((thread = app.getThread()) != null) { |
| try { |
| thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); |
| app.mProfile.setPendingUiClean(false); |
| } catch (RemoteException e) { |
| |
| } |
| } |
| } |
| }); |
| return false; |
| } |
| |
| if (memFactor != ADJ_MEM_FACTOR_NORMAL) { |
| if (mLowRamStartTime == 0) { |
| mLowRamStartTime = now; |
| } |
| int fgTrimLevel; |
| switch (memFactor) { |
| case ADJ_MEM_FACTOR_CRITICAL: |
| fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; |
| break; |
| case ADJ_MEM_FACTOR_LOW: |
| fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; |
| break; |
| default: |
| fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; |
| break; |
| } |
| int factor = numTrimming / 3; |
| int minFactor = 2; |
| if (mHasHomeProcess) minFactor++; |
| if (mHasPreviousProcess) minFactor++; |
| if (factor < minFactor) factor = minFactor; |
| final int actualFactor = factor; |
| final int[] step = {0}; |
| final int[] curLevel = {ComponentCallbacks2.TRIM_MEMORY_COMPLETE}; |
| mService.mProcessList.forEachLruProcessesLOSP(true, app -> { |
| final ProcessProfileRecord profile = app.mProfile; |
| final int trimMemoryLevel = profile.getTrimMemoryLevel(); |
| final ProcessStateRecord state = app.mState; |
| final int curProcState = state.getCurProcState(); |
| IApplicationThread thread; |
| if (allChanged || state.hasProcStateChanged()) { |
| mService.setProcessTrackerStateLOSP(app, trackerMemFactor); |
| state.setProcStateChanged(false); |
| } |
| trimMemoryUiHiddenIfNecessaryLSP(app); |
| if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) { |
| scheduleTrimMemoryLSP(app, curLevel[0], "Trimming memory of "); |
| profile.setTrimMemoryLevel(curLevel[0]); |
| step[0]++; |
| if (step[0] >= actualFactor) { |
| step[0] = 0; |
| switch (curLevel[0]) { |
| case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: |
| curLevel[0] = ComponentCallbacks2.TRIM_MEMORY_MODERATE; |
| break; |
| case ComponentCallbacks2.TRIM_MEMORY_MODERATE: |
| curLevel[0] = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; |
| break; |
| } |
| } |
| } else if (curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT |
| && !app.isKilledByAm()) { |
| scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_BACKGROUND, |
| "Trimming memory of heavy-weight "); |
| profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); |
| } else { |
| scheduleTrimMemoryLSP(app, fgTrimLevel, "Trimming memory of fg "); |
| profile.setTrimMemoryLevel(fgTrimLevel); |
| } |
| }); |
| } else { |
| if (mLowRamStartTime != 0) { |
| mLowRamTimeSinceLastIdle += now - mLowRamStartTime; |
| mLowRamStartTime = 0; |
| } |
| mService.mProcessList.forEachLruProcessesLOSP(true, app -> { |
| final ProcessProfileRecord profile = app.mProfile; |
| final IApplicationThread thread; |
| final ProcessStateRecord state = app.mState; |
| if (allChanged || state.hasProcStateChanged()) { |
| mService.setProcessTrackerStateLOSP(app, trackerMemFactor); |
| state.setProcStateChanged(false); |
| } |
| trimMemoryUiHiddenIfNecessaryLSP(app); |
| profile.setTrimMemoryLevel(0); |
| }); |
| } |
| return allChanged; |
| } |
| |
| @GuardedBy({"mService", "mProcLock"}) |
| private void trimMemoryUiHiddenIfNecessaryLSP(ProcessRecord app) { |
| if ((app.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND |
| || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) { |
| // If this application is now in the background and it |
| // had done UI, then give it the special trim level to |
| // have it free UI resources. |
| scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN, |
| "Trimming memory of bg-ui "); |
| app.mProfile.setPendingUiClean(false); |
| } |
| } |
| |
| @GuardedBy({"mService", "mProcLock"}) |
| private void scheduleTrimMemoryLSP(ProcessRecord app, int level, String msg) { |
| IApplicationThread thread; |
| if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) { |
| try { |
| if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { |
| Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level); |
| } |
| mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app, |
| CachedAppOptimizer.UNFREEZE_REASON_TRIM_MEMORY); |
| thread.scheduleTrimMemory(level); |
| } catch (RemoteException e) { |
| } |
| } |
| } |
| |
| @GuardedBy("mProcLock") |
| long getLowRamTimeSinceIdleLPr(long now) { |
| return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now - mLowRamStartTime) : 0); |
| } |
| |
| /** |
| * Ask a given process to GC right now. |
| */ |
| @GuardedBy("mProfilerLock") |
| private void performAppGcLPf(ProcessRecord app) { |
| try { |
| final ProcessProfileRecord profile = app.mProfile; |
| profile.setLastRequestedGc(SystemClock.uptimeMillis()); |
| IApplicationThread thread = profile.getThread(); |
| if (thread != null) { |
| if (profile.getReportLowMemory()) { |
| profile.setReportLowMemory(false); |
| thread.scheduleLowMemory(); |
| } else { |
| thread.processInBackground(); |
| } |
| } |
| } catch (Exception e) { |
| // whatever. |
| } |
| } |
| |
| /** |
| * Perform GCs on all processes that are waiting for it, but only |
| * if things are idle. |
| */ |
| @GuardedBy("mProfilerLock") |
| private void performAppGcsLPf() { |
| if (mProcessesToGc.size() <= 0) { |
| return; |
| } |
| while (mProcessesToGc.size() > 0) { |
| final ProcessRecord proc = mProcessesToGc.remove(0); |
| final ProcessProfileRecord profile = proc.mProfile; |
| if (profile.getCurRawAdj() > ProcessList.PERCEPTIBLE_APP_ADJ |
| || profile.getReportLowMemory()) { |
| if ((profile.getLastRequestedGc() + mService.mConstants.GC_MIN_INTERVAL) |
| <= SystemClock.uptimeMillis()) { |
| // To avoid spamming the system, we will GC processes one |
| // at a time, waiting a few seconds between each. |
| performAppGcLPf(proc); |
| scheduleAppGcsLPf(); |
| return; |
| } else { |
| // It hasn't been long enough since we last GCed this |
| // process... put it in the list to wait for its time. |
| addProcessToGcListLPf(proc); |
| break; |
| } |
| } |
| } |
| |
| scheduleAppGcsLPf(); |
| } |
| |
| /** |
| * If all looks good, perform GCs on all processes waiting for them. |
| */ |
| @GuardedBy("mService") |
| final void performAppGcsIfAppropriateLocked() { |
| synchronized (mProfilerLock) { |
| if (mService.canGcNowLocked()) { |
| performAppGcsLPf(); |
| return; |
| } |
| // Still not idle, wait some more. |
| scheduleAppGcsLPf(); |
| } |
| } |
| |
| /** |
| * Schedule the execution of all pending app GCs. |
| */ |
| @GuardedBy("mProfilerLock") |
| final void scheduleAppGcsLPf() { |
| mService.mHandler.removeMessages(GC_BACKGROUND_PROCESSES_MSG); |
| |
| if (mProcessesToGc.size() > 0) { |
| // Schedule a GC for the time to the next process. |
| ProcessRecord proc = mProcessesToGc.get(0); |
| Message msg = mService.mHandler.obtainMessage(GC_BACKGROUND_PROCESSES_MSG); |
| |
| long when = proc.mProfile.getLastRequestedGc() + mService.mConstants.GC_MIN_INTERVAL; |
| long now = SystemClock.uptimeMillis(); |
| if (when < (now + mService.mConstants.GC_TIMEOUT)) { |
| when = now + mService.mConstants.GC_TIMEOUT; |
| } |
| mService.mHandler.sendMessageAtTime(msg, when); |
| } |
| } |
| |
| /** |
| * Add a process to the array of processes waiting to be GCed. Keeps the |
| * list in sorted order by the last GC time. The process can't already be |
| * on the list. |
| */ |
| @GuardedBy("mProfilerLock") |
| private void addProcessToGcListLPf(ProcessRecord proc) { |
| boolean added = false; |
| for (int i = mProcessesToGc.size() - 1; i >= 0; i--) { |
| if (mProcessesToGc.get(i).mProfile.getLastRequestedGc() |
| < proc.mProfile.getLastRequestedGc()) { |
| added = true; |
| mProcessesToGc.add(i + 1, proc); |
| break; |
| } |
| } |
| if (!added) { |
| mProcessesToGc.add(0, proc); |
| } |
| } |
| |
| @GuardedBy("mService") |
| final void doLowMemReportIfNeededLocked(ProcessRecord dyingProc) { |
| // If there are no longer any background processes running, |
| // and the app that died was not running instrumentation, |
| // then tell everyone we are now low on memory. |
| if (!mService.mProcessList.haveBackgroundProcessLOSP()) { |
| boolean doReport = Build.IS_DEBUGGABLE; |
| final long now = SystemClock.uptimeMillis(); |
| if (doReport) { |
| if (now < (mLastMemUsageReportTime + 5 * 60 * 1000)) { |
| doReport = false; |
| } else { |
| mLastMemUsageReportTime = now; |
| } |
| } |
| final int lruSize = mService.mProcessList.getLruSizeLOSP(); |
| final ArrayList<ProcessMemInfo> memInfos = doReport |
| ? new ArrayList<ProcessMemInfo>(lruSize) : null; |
| EventLogTags.writeAmLowMemory(lruSize); |
| mService.mProcessList.forEachLruProcessesLOSP(false, rec -> { |
| if (rec == dyingProc || rec.getThread() == null) { |
| return; |
| } |
| final ProcessStateRecord state = rec.mState; |
| if (memInfos != null) { |
| memInfos.add(new ProcessMemInfo(rec.processName, rec.getPid(), |
| state.getSetAdj(), state.getSetProcState(), |
| state.getAdjType(), state.makeAdjReason())); |
| } |
| final ProcessProfileRecord profile = rec.mProfile; |
| if ((profile.getLastLowMemory() + mService.mConstants.GC_MIN_INTERVAL) <= now) { |
| // The low memory report is overriding any current |
| // state for a GC request. Make sure to do |
| // heavy/important/visible/foreground processes first. |
| synchronized (mProfilerLock) { |
| if (state.getSetAdj() <= ProcessList.HEAVY_WEIGHT_APP_ADJ) { |
| profile.setLastRequestedGc(0); |
| } else { |
| profile.setLastRequestedGc(profile.getLastLowMemory()); |
| } |
| profile.setReportLowMemory(true); |
| profile.setLastLowMemory(now); |
| mProcessesToGc.remove(rec); |
| addProcessToGcListLPf(rec); |
| } |
| } |
| }); |
| if (doReport) { |
| Message msg = mService.mHandler.obtainMessage(REPORT_MEM_USAGE_MSG, memInfos); |
| mService.mHandler.sendMessage(msg); |
| } |
| } |
| synchronized (mProfilerLock) { |
| scheduleAppGcsLPf(); |
| } |
| } |
| |
| void reportMemUsage(ArrayList<ProcessMemInfo> memInfos) { |
| final SparseArray<ProcessMemInfo> infoMap = new SparseArray<>(memInfos.size()); |
| for (int i = 0, size = memInfos.size(); i < size; i++) { |
| ProcessMemInfo mi = memInfos.get(i); |
| infoMap.put(mi.pid, mi); |
| } |
| updateCpuStatsNow(); |
| long[] memtrackTmp = new long[4]; |
| long[] swaptrackTmp = new long[2]; |
| // Get a list of Stats that have vsize > 0 |
| final List<ProcessCpuTracker.Stats> stats = getCpuStats(st -> st.vsize > 0); |
| final int statsCount = stats.size(); |
| long totalMemtrackGraphics = 0; |
| long totalMemtrackGl = 0; |
| for (int i = 0; i < statsCount; i++) { |
| ProcessCpuTracker.Stats st = stats.get(i); |
| long pss = Debug.getPss(st.pid, swaptrackTmp, memtrackTmp); |
| if (pss > 0) { |
| if (infoMap.indexOfKey(st.pid) < 0) { |
| ProcessMemInfo mi = new ProcessMemInfo(st.name, st.pid, |
| ProcessList.NATIVE_ADJ, -1, "native", null); |
| mi.pss = pss; |
| mi.swapPss = swaptrackTmp[1]; |
| mi.memtrack = memtrackTmp[0]; |
| totalMemtrackGraphics += memtrackTmp[1]; |
| totalMemtrackGl += memtrackTmp[2]; |
| memInfos.add(mi); |
| } |
| } |
| } |
| |
| long totalPss = 0; |
| long totalSwapPss = 0; |
| long totalMemtrack = 0; |
| for (int i = 0, size = memInfos.size(); i < size; i++) { |
| ProcessMemInfo mi = memInfos.get(i); |
| if (mi.pss == 0) { |
| mi.pss = Debug.getPss(mi.pid, swaptrackTmp, memtrackTmp); |
| mi.swapPss = swaptrackTmp[1]; |
| mi.memtrack = memtrackTmp[0]; |
| totalMemtrackGraphics += memtrackTmp[1]; |
| totalMemtrackGl += memtrackTmp[2]; |
| } |
| totalPss += mi.pss; |
| totalSwapPss += mi.swapPss; |
| totalMemtrack += mi.memtrack; |
| } |
| Collections.sort(memInfos, new Comparator<ProcessMemInfo>() { |
| @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) { |
| if (lhs.oomAdj != rhs.oomAdj) { |
| return lhs.oomAdj < rhs.oomAdj ? -1 : 1; |
| } |
| if (lhs.pss != rhs.pss) { |
| return lhs.pss < rhs.pss ? 1 : -1; |
| } |
| return 0; |
| } |
| }); |
| |
| StringBuilder tag = new StringBuilder(128); |
| StringBuilder stack = new StringBuilder(128); |
| tag.append("Low on memory -- "); |
| appendMemBucket(tag, totalPss, "total", false); |
| appendMemBucket(stack, totalPss, "total", true); |
| |
| StringBuilder fullNativeBuilder = new StringBuilder(1024); |
| StringBuilder shortNativeBuilder = new StringBuilder(1024); |
| StringBuilder fullJavaBuilder = new StringBuilder(1024); |
| |
| boolean firstLine = true; |
| int lastOomAdj = Integer.MIN_VALUE; |
| long extraNativeRam = 0; |
| long extraNativeMemtrack = 0; |
| long cachedPss = 0; |
| for (int i = 0, size = memInfos.size(); i < size; i++) { |
| ProcessMemInfo mi = memInfos.get(i); |
| |
| if (mi.oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) { |
| cachedPss += mi.pss; |
| } |
| |
| if (mi.oomAdj != ProcessList.NATIVE_ADJ |
| && (mi.oomAdj < ProcessList.SERVICE_ADJ |
| || mi.oomAdj == ProcessList.HOME_APP_ADJ |
| || mi.oomAdj == ProcessList.PREVIOUS_APP_ADJ)) { |
| if (lastOomAdj != mi.oomAdj) { |
| lastOomAdj = mi.oomAdj; |
| if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) { |
| tag.append(" / "); |
| } |
| if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ) { |
| if (firstLine) { |
| stack.append(":"); |
| firstLine = false; |
| } |
| stack.append("\n\t at "); |
| } else { |
| stack.append("$"); |
| } |
| } else { |
| tag.append(" "); |
| stack.append("$"); |
| } |
| if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) { |
| appendMemBucket(tag, mi.pss, mi.name, false); |
| } |
| appendMemBucket(stack, mi.pss, mi.name, true); |
| if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ |
| && ((i + 1) >= size || memInfos.get(i + 1).oomAdj != lastOomAdj)) { |
| stack.append("("); |
| for (int k = 0; k < DUMP_MEM_OOM_ADJ.length; k++) { |
| if (DUMP_MEM_OOM_ADJ[k] == mi.oomAdj) { |
| stack.append(DUMP_MEM_OOM_LABEL[k]); |
| stack.append(":"); |
| stack.append(DUMP_MEM_OOM_ADJ[k]); |
| } |
| } |
| stack.append(")"); |
| } |
| } |
| |
| appendMemInfo(fullNativeBuilder, mi); |
| if (mi.oomAdj == ProcessList.NATIVE_ADJ) { |
| // The short form only has native processes that are >= 512K. |
| if (mi.pss >= 512) { |
| appendMemInfo(shortNativeBuilder, mi); |
| } else { |
| extraNativeRam += mi.pss; |
| extraNativeMemtrack += mi.memtrack; |
| } |
| } else { |
| // Short form has all other details, but if we have collected RAM |
| // from smaller native processes let's dump a summary of that. |
| if (extraNativeRam > 0) { |
| appendBasicMemEntry(shortNativeBuilder, ProcessList.NATIVE_ADJ, |
| -1, extraNativeRam, extraNativeMemtrack, "(Other native)"); |
| shortNativeBuilder.append('\n'); |
| extraNativeRam = 0; |
| } |
| appendMemInfo(fullJavaBuilder, mi); |
| } |
| } |
| |
| fullJavaBuilder.append(" "); |
| ProcessList.appendRamKb(fullJavaBuilder, totalPss); |
| fullJavaBuilder.append(": TOTAL"); |
| if (totalMemtrack > 0) { |
| fullJavaBuilder.append(" ("); |
| fullJavaBuilder.append(stringifyKBSize(totalMemtrack)); |
| fullJavaBuilder.append(" memtrack)"); |
| } |
| fullJavaBuilder.append("\n"); |
| |
| MemInfoReader memInfo = new MemInfoReader(); |
| memInfo.readMemInfo(); |
| final long[] infos = memInfo.getRawInfo(); |
| |
| StringBuilder memInfoBuilder = new StringBuilder(1024); |
| Debug.getMemInfo(infos); |
| memInfoBuilder.append(" MemInfo: "); |
| memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SLAB])).append(" slab, "); |
| memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SHMEM])).append(" shmem, "); |
| memInfoBuilder.append(stringifyKBSize( |
| infos[Debug.MEMINFO_VM_ALLOC_USED])).append(" vm alloc, "); |
| memInfoBuilder.append(stringifyKBSize( |
| infos[Debug.MEMINFO_PAGE_TABLES])).append(" page tables "); |
| memInfoBuilder.append(stringifyKBSize( |
| infos[Debug.MEMINFO_KERNEL_STACK])).append(" kernel stack\n"); |
| memInfoBuilder.append(" "); |
| memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_BUFFERS])).append(" buffers, "); |
| memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_CACHED])).append(" cached, "); |
| memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_MAPPED])).append(" mapped, "); |
| memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_FREE])).append(" free\n"); |
| if (infos[Debug.MEMINFO_ZRAM_TOTAL] != 0) { |
| memInfoBuilder.append(" ZRAM: "); |
| memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_ZRAM_TOTAL])); |
| memInfoBuilder.append(" RAM, "); |
| memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SWAP_TOTAL])); |
| memInfoBuilder.append(" swap total, "); |
| memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SWAP_FREE])); |
| memInfoBuilder.append(" swap free\n"); |
| } |
| final long[] ksm = getKsmInfo(); |
| if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0 |
| || ksm[KSM_VOLATILE] != 0) { |
| memInfoBuilder.append(" KSM: "); |
| memInfoBuilder.append(stringifyKBSize(ksm[KSM_SHARING])); |
| memInfoBuilder.append(" saved from shared "); |
| memInfoBuilder.append(stringifyKBSize(ksm[KSM_SHARED])); |
| memInfoBuilder.append("\n "); |
| memInfoBuilder.append(stringifyKBSize(ksm[KSM_UNSHARED])); |
| memInfoBuilder.append(" unshared; "); |
| memInfoBuilder.append(stringifyKBSize(ksm[KSM_VOLATILE])); |
| memInfoBuilder.append(" volatile\n"); |
| } |
| memInfoBuilder.append(" Free RAM: "); |
| memInfoBuilder.append(stringifyKBSize(cachedPss + memInfo.getCachedSizeKb() |
| + memInfo.getFreeSizeKb())); |
| memInfoBuilder.append("\n"); |
| long kernelUsed = memInfo.getKernelUsedSizeKb(); |
| final long ionHeap = Debug.getIonHeapsSizeKb(); |
| final long ionPool = Debug.getIonPoolsSizeKb(); |
| final long dmabufMapped = Debug.getDmabufMappedSizeKb(); |
| if (ionHeap >= 0 && ionPool >= 0) { |
| final long ionUnmapped = ionHeap - dmabufMapped; |
| memInfoBuilder.append(" ION: "); |
| memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool)); |
| memInfoBuilder.append("\n"); |
| kernelUsed += ionUnmapped; |
| // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being |
| // set on ION VMAs, however it might be included by the memtrack HAL. |
| // Replace memtrack HAL reported Graphics category with mapped dmabufs |
| totalPss -= totalMemtrackGraphics; |
| totalPss += dmabufMapped; |
| } else { |
| final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb(); |
| if (totalExportedDmabuf >= 0) { |
| final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped; |
| memInfoBuilder.append("DMA-BUF: "); |
| memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf)); |
| memInfoBuilder.append("\n"); |
| // Account unmapped dmabufs as part of kernel memory allocations |
| kernelUsed += dmabufUnmapped; |
| // Replace memtrack HAL reported Graphics category with mapped dmabufs |
| totalPss -= totalMemtrackGraphics; |
| totalPss += dmabufMapped; |
| } |
| // These are included in the totalExportedDmabuf above and hence do not need to be added |
| // to kernelUsed. |
| final long totalExportedDmabufHeap = Debug.getDmabufHeapTotalExportedKb(); |
| if (totalExportedDmabufHeap >= 0) { |
| memInfoBuilder.append("DMA-BUF Heap: "); |
| memInfoBuilder.append(stringifyKBSize(totalExportedDmabufHeap)); |
| memInfoBuilder.append("\n"); |
| } |
| |
| final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); |
| if (totalDmabufHeapPool >= 0) { |
| memInfoBuilder.append("DMA-BUF Heaps pool: "); |
| memInfoBuilder.append(stringifyKBSize(totalDmabufHeapPool)); |
| memInfoBuilder.append("\n"); |
| } |
| } |
| |
| final long gpuUsage = Debug.getGpuTotalUsageKb(); |
| if (gpuUsage >= 0) { |
| final long gpuPrivateUsage = Debug.getGpuPrivateMemoryKb(); |
| if (gpuPrivateUsage >= 0) { |
| final long gpuDmaBufUsage = gpuUsage - gpuPrivateUsage; |
| memInfoBuilder.append(" GPU: "); |
| memInfoBuilder.append(stringifyKBSize(gpuUsage)); |
| memInfoBuilder.append(" ("); |
| memInfoBuilder.append(stringifyKBSize(gpuDmaBufUsage)); |
| memInfoBuilder.append(" dmabuf + "); |
| memInfoBuilder.append(stringifyKBSize(gpuPrivateUsage)); |
| memInfoBuilder.append(" private)\n"); |
| // Replace memtrack HAL reported GL category with private GPU allocations and |
| // account it as part of kernel memory allocations |
| totalPss -= totalMemtrackGl; |
| kernelUsed += gpuPrivateUsage; |
| } else { |
| memInfoBuilder.append(" GPU: "); |
| memInfoBuilder.append(stringifyKBSize(gpuUsage)); |
| memInfoBuilder.append("\n"); |
| } |
| |
| } |
| memInfoBuilder.append(" Used RAM: "); |
| memInfoBuilder.append(stringifyKBSize( |
| totalPss - cachedPss + kernelUsed)); |
| memInfoBuilder.append("\n"); |
| |
| // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of |
| // memInfo.getCachedSizeKb(). |
| memInfoBuilder.append(" Lost RAM: "); |
| memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb() |
| - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() |
| - kernelUsed - memInfo.getZramTotalSizeKb())); |
| memInfoBuilder.append("\n"); |
| Slog.i(TAG, "Low on memory:"); |
| Slog.i(TAG, shortNativeBuilder.toString()); |
| Slog.i(TAG, fullJavaBuilder.toString()); |
| Slog.i(TAG, memInfoBuilder.toString()); |
| |
| StringBuilder dropBuilder = new StringBuilder(1024); |
| dropBuilder.append("Low on memory:"); |
| dropBuilder.append(stack); |
| dropBuilder.append('\n'); |
| dropBuilder.append(fullNativeBuilder); |
| dropBuilder.append(fullJavaBuilder); |
| dropBuilder.append('\n'); |
| dropBuilder.append(memInfoBuilder); |
| dropBuilder.append('\n'); |
| StringWriter catSw = new StringWriter(); |
| synchronized (mService) { |
| PrintWriter catPw = new FastPrintWriter(catSw, false, 256); |
| String[] emptyArgs = new String[] { }; |
| catPw.println(); |
| synchronized (mProcLock) { |
| mService.mProcessList.dumpProcessesLSP(null, catPw, emptyArgs, 0, false, null, -1); |
| } |
| catPw.println(); |
| mService.mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0, |
| false, null).dumpLocked(); |
| catPw.println(); |
| mService.mAtmInternal.dump(DUMP_ACTIVITIES_CMD, null, catPw, emptyArgs, 0, false, false, |
| null, INVALID_DISPLAY); |
| catPw.flush(); |
| } |
| dropBuilder.append(catSw.toString()); |
| FrameworkStatsLog.write(FrameworkStatsLog.LOW_MEM_REPORTED); |
| mService.addErrorToDropBox("lowmem", null, "system_server", null, |
| null, null, tag.toString(), dropBuilder.toString(), null, null, null, null, null, |
| null); |
| synchronized (mService) { |
| long now = SystemClock.uptimeMillis(); |
| if (mLastMemUsageReportTime < now) { |
| mLastMemUsageReportTime = now; |
| } |
| } |
| } |
| |
| @GuardedBy("mService") |
| private void handleMemoryPressureChangedLocked(@MemFactor int oldMemFactor, |
| @MemFactor int newMemFactor) { |
| mService.mServices.rescheduleServiceRestartOnMemoryPressureIfNeededLocked( |
| oldMemFactor, newMemFactor, "mem-pressure-event", SystemClock.uptimeMillis()); |
| } |
| |
| @GuardedBy("mProfilerLock") |
| private void stopProfilerLPf(ProcessRecord proc, int profileType) { |
| if (proc == null || proc == mProfileData.getProfileProc()) { |
| proc = mProfileData.getProfileProc(); |
| profileType = mProfileType; |
| clearProfilerLPf(); |
| } |
| if (proc == null) { |
| return; |
| } |
| final IApplicationThread thread = proc.mProfile.getThread(); |
| if (thread == null) { |
| return; |
| } |
| try { |
| thread.profilerControl(false, null, profileType); |
| } catch (RemoteException e) { |
| throw new IllegalStateException("Process disappeared"); |
| } |
| } |
| |
| @GuardedBy("mProfilerLock") |
| void clearProfilerLPf() { |
| if (mProfileData.getProfilerInfo() != null |
| && mProfileData.getProfilerInfo().profileFd != null) { |
| try { |
| mProfileData.getProfilerInfo().profileFd.close(); |
| } catch (IOException e) { |
| } |
| } |
| mProfileData.setProfileApp(null); |
| mProfileData.setProfileProc(null); |
| mProfileData.setProfilerInfo(null); |
| } |
| |
| @GuardedBy("mProfilerLock") |
| void clearProfilerLPf(ProcessRecord app) { |
| if (mProfileData.getProfileProc() == null |
| || mProfileData.getProfilerInfo() == null |
| || mProfileData.getProfileProc() != app) { |
| return; |
| } |
| clearProfilerLPf(); |
| } |
| |
| @GuardedBy("mProfilerLock") |
| boolean profileControlLPf(ProcessRecord proc, boolean start, |
| ProfilerInfo profilerInfo, int profileType) { |
| try { |
| if (start) { |
| stopProfilerLPf(null, 0); |
| mService.setProfileApp(proc.info, proc.processName, profilerInfo, |
| proc.isSdkSandbox ? proc.getClientInfoForSdkSandbox() : null); |
| mProfileData.setProfileProc(proc); |
| mProfileType = profileType; |
| ParcelFileDescriptor fd = profilerInfo.profileFd; |
| try { |
| fd = fd.dup(); |
| } catch (IOException e) { |
| fd = null; |
| } |
| profilerInfo.profileFd = fd; |
| proc.mProfile.getThread().profilerControl(start, profilerInfo, profileType); |
| fd = null; |
| try { |
| mProfileData.getProfilerInfo().profileFd.close(); |
| } catch (IOException e) { |
| } |
| mProfileData.getProfilerInfo().profileFd = null; |
| |
| if (proc.getPid() == mService.MY_PID) { |
| // When profiling the system server itself, avoid closing the file |
| // descriptor, as profilerControl will not create a copy. |
| // Note: it is also not correct to just set profileFd to null, as the |
| // whole ProfilerInfo instance is passed down! |
| profilerInfo = null; |
| } |
| } else { |
| stopProfilerLPf(proc, profileType); |
| if (profilerInfo != null && profilerInfo.profileFd != null) { |
| try { |
| profilerInfo.profileFd.close(); |
| } catch (IOException e) { |
| } |
| } |
| } |
| |
| return true; |
| } catch (RemoteException e) { |
| throw new IllegalStateException("Process disappeared"); |
| } finally { |
| if (profilerInfo != null && profilerInfo.profileFd != null) { |
| try { |
| profilerInfo.profileFd.close(); |
| } catch (IOException e) { |
| } |
| } |
| } |
| } |
| |
| @GuardedBy("mProfilerLock") |
| void setProfileAppLPf(String processName, ProfilerInfo profilerInfo) { |
| mProfileData.setProfileApp(processName); |
| |
| if (mProfileData.getProfilerInfo() != null) { |
| if (mProfileData.getProfilerInfo().profileFd != null) { |
| try { |
| mProfileData.getProfilerInfo().profileFd.close(); |
| } catch (IOException e) { |
| } |
| } |
| } |
| mProfileData.setProfilerInfo(new ProfilerInfo(profilerInfo)); |
| mProfileType = 0; |
| } |
| |
| @GuardedBy("mProfilerLock") |
| void setProfileProcLPf(ProcessRecord proc) { |
| mProfileData.setProfileProc(proc); |
| } |
| |
| @GuardedBy("mProfilerLock") |
| void setAgentAppLPf(@NonNull String packageName, @Nullable String agent) { |
| if (agent == null) { |
| if (mAppAgentMap != null) { |
| mAppAgentMap.remove(packageName); |
| if (mAppAgentMap.isEmpty()) { |
| mAppAgentMap = null; |
| } |
| } |
| } else { |
| if (mAppAgentMap == null) { |
| mAppAgentMap = new HashMap<>(); |
| } |
| if (mAppAgentMap.size() >= 100) { |
| // Limit the size of the map, to avoid OOMEs. |
| Slog.e(TAG, "App agent map has too many entries, cannot add " + packageName |
| + "/" + agent); |
| return; |
| } |
| mAppAgentMap.put(packageName, agent); |
| } |
| } |
| |
| void updateCpuStats() { |
| final long now = SystemClock.uptimeMillis(); |
| if (mLastCpuTime.get() >= now - MONITOR_CPU_MIN_TIME) { |
| return; |
| } |
| if (mProcessCpuMutexFree.compareAndSet(true, false)) { |
| synchronized (mProcessCpuThread) { |
| mProcessCpuThread.notify(); |
| } |
| } |
| } |
| |
| void updateCpuStatsNow() { |
| final boolean monitorPhantomProcs = mService.mSystemReady && FeatureFlagUtils.isEnabled( |
| mService.mContext, SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); |
| synchronized (mProcessCpuTracker) { |
| mProcessCpuMutexFree.set(false); |
| final long now = SystemClock.uptimeMillis(); |
| boolean haveNewCpuStats = false; |
| |
| if (MONITOR_CPU_USAGE |
| && mLastCpuTime.get() < (now - MONITOR_CPU_MIN_TIME)) { |
| mLastCpuTime.set(now); |
| mProcessCpuTracker.update(); |
| if (mProcessCpuTracker.hasGoodLastStats()) { |
| haveNewCpuStats = true; |
| //Slog.i(TAG, mProcessCpu.printCurrentState()); |
| //Slog.i(TAG, "Total CPU usage: " |
| // + mProcessCpu.getTotalCpuPercent() + "%"); |
| |
| // Slog the cpu usage if the property is set. |
| if ("true".equals(SystemProperties.get("events.cpu"))) { |
| int user = mProcessCpuTracker.getLastUserTime(); |
| int system = mProcessCpuTracker.getLastSystemTime(); |
| int iowait = mProcessCpuTracker.getLastIoWaitTime(); |
| int irq = mProcessCpuTracker.getLastIrqTime(); |
| int softIrq = mProcessCpuTracker.getLastSoftIrqTime(); |
| int idle = mProcessCpuTracker.getLastIdleTime(); |
| |
| int total = user + system + iowait + irq + softIrq + idle; |
| if (total == 0) total = 1; |
| |
| EventLogTags.writeCpu( |
| ((user + system + iowait + irq + softIrq) * 100) / total, |
| (user * 100) / total, |
| (system * 100) / total, |
| (iowait * 100) / total, |
| (irq * 100) / total, |
| (softIrq * 100) / total); |
| } |
| } |
| } |
| |
| if (monitorPhantomProcs && haveNewCpuStats) { |
| mService.mPhantomProcessList.updateProcessCpuStatesLocked(mProcessCpuTracker); |
| } |
| |
| final BatteryStatsImpl bstats = mService.mBatteryStatsService.getActiveStatistics(); |
| synchronized (bstats) { |
| if (haveNewCpuStats) { |
| if (bstats.startAddingCpuStatsLocked()) { |
| int totalUTime = 0; |
| int totalSTime = 0; |
| final int statsCount = mProcessCpuTracker.countStats(); |
| final long elapsedRealtime = SystemClock.elapsedRealtime(); |
| final long uptime = SystemClock.uptimeMillis(); |
| synchronized (mService.mPidsSelfLocked) { |
| for (int i = 0; i < statsCount; i++) { |
| ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); |
| if (!st.working) { |
| continue; |
| } |
| ProcessRecord pr = mService.mPidsSelfLocked.get(st.pid); |
| totalUTime += st.rel_utime; |
| totalSTime += st.rel_stime; |
| if (pr != null) { |
| final ProcessProfileRecord profile = pr.mProfile; |
| BatteryStatsImpl.Uid.Proc ps = profile.getCurProcBatteryStats(); |
| if (ps == null || !ps.isActive()) { |
| profile.setCurProcBatteryStats( |
| ps = bstats.getProcessStatsLocked( |
| pr.info.uid, pr.processName, |
| elapsedRealtime, uptime)); |
| } |
| ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); |
| final long curCpuTime = profile.mCurCpuTime.addAndGet( |
| st.rel_utime + st.rel_stime); |
| profile.mLastCpuTime.compareAndSet(0, curCpuTime); |
| } else { |
| BatteryStatsImpl.Uid.Proc ps = |
| (BatteryStatsImpl.Uid.Proc) st.batteryStats; |
| if (ps == null || !ps.isActive()) { |
| st.batteryStats = ps = bstats.getProcessStatsLocked( |
| st.uid, st.name, elapsedRealtime, uptime); |
| } |
| ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); |
| } |
| } |
| } |
| |
| final int userTime = mProcessCpuTracker.getLastUserTime(); |
| final int systemTime = mProcessCpuTracker.getLastSystemTime(); |
| final int iowaitTime = mProcessCpuTracker.getLastIoWaitTime(); |
| final int irqTime = mProcessCpuTracker.getLastIrqTime(); |
| final int softIrqTime = mProcessCpuTracker.getLastSoftIrqTime(); |
| final int idleTime = mProcessCpuTracker.getLastIdleTime(); |
| bstats.addCpuStatsLocked(totalUTime, totalSTime, userTime, |
| systemTime, iowaitTime, irqTime, softIrqTime, idleTime); |
| } |
| bstats.finishAddingCpuStatsLocked(); |
| } |
| |
| if (mLastWriteTime < (now - BATTERY_STATS_TIME)) { |
| mLastWriteTime = now; |
| mService.mBatteryStatsService.scheduleWriteToDisk(); |
| } |
| } |
| } |
| } |
| |
| long getCpuTimeForPid(int pid) { |
| return mProcessCpuTracker.getCpuTimeForPid(pid); |
| } |
| |
| long getCpuDelayTimeForPid(int pid) { |
| return mProcessCpuTracker.getCpuDelayTimeForPid(pid); |
| } |
| |
| List<ProcessCpuTracker.Stats> getCpuStats(Predicate<ProcessCpuTracker.Stats> predicate) { |
| synchronized (mProcessCpuTracker) { |
| return mProcessCpuTracker.getStats(st -> predicate.test(st)); |
| } |
| } |
| |
| void forAllCpuStats(Consumer<ProcessCpuTracker.Stats> consumer) { |
| synchronized (mProcessCpuTracker) { |
| final int numOfStats = mProcessCpuTracker.countStats(); |
| for (int i = 0; i < numOfStats; i++) { |
| consumer.accept(mProcessCpuTracker.getStats(i)); |
| } |
| } |
| } |
| |
| private class ProcessCpuThread extends Thread { |
| ProcessCpuThread(String name) { |
| super(name); |
| } |
| |
| @Override |
| public void run() { |
| synchronized (mProcessCpuTracker) { |
| mProcessCpuInitLatch.countDown(); |
| mProcessCpuTracker.init(); |
| } |
| while (true) { |
| try { |
| try { |
| synchronized (this) { |
| final long now = SystemClock.uptimeMillis(); |
| long nextCpuDelay = (mLastCpuTime.get() + MONITOR_CPU_MAX_TIME) - now; |
| long nextWriteDelay = (mLastWriteTime + BATTERY_STATS_TIME) - now; |
| //Slog.i(TAG, "Cpu delay=" + nextCpuDelay |
| // + ", write delay=" + nextWriteDelay); |
| if (nextWriteDelay < nextCpuDelay) { |
| nextCpuDelay = nextWriteDelay; |
| } |
| if (nextCpuDelay > 0) { |
| mProcessCpuMutexFree.set(true); |
| this.wait(nextCpuDelay); |
| } |
| } |
| } catch (InterruptedException e) { |
| } |
| updateCpuStatsNow(); |
| } catch (Exception e) { |
| Slog.e(TAG, "Unexpected exception collecting process stats", e); |
| } |
| } |
| } |
| } |
| |
| class CpuBinder extends Binder { |
| private final PriorityDump.PriorityDumper mPriorityDumper = |
| new PriorityDump.PriorityDumper() { |
| @Override |
| public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, |
| boolean asProto) { |
| if (!DumpUtils.checkDumpAndUsageStatsPermission(mService.mContext, "cpuinfo", pw)) { |
| return; |
| } |
| synchronized (mProcessCpuTracker) { |
| if (asProto) { |
| mProcessCpuTracker.dumpProto(fd); |
| return; |
| } |
| pw.print(mProcessCpuTracker.printCurrentLoad()); |
| pw.print(mProcessCpuTracker.printCurrentState( |
| SystemClock.uptimeMillis())); |
| } |
| } |
| }; |
| |
| @Override |
| protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| PriorityDump.dump(mPriorityDumper, fd, pw, args); |
| } |
| } |
| |
| void setCpuInfoService() { |
| if (MONITOR_CPU_USAGE) { |
| ServiceManager.addService("cpuinfo", new CpuBinder(), |
| /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); |
| } |
| } |
| |
| AppProfiler(ActivityManagerService service, Looper bgLooper, LowMemDetector detector) { |
| mService = service; |
| mProcLock = service.mProcLock; |
| mBgHandler = new BgHandler(bgLooper); |
| mLowMemDetector = detector; |
| mProcessCpuThread = new ProcessCpuThread("CpuTracker"); |
| } |
| |
| void retrieveSettings() { |
| final long pssDeferralMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| ACTIVITY_START_PSS_DEFER_CONFIG, 0L); |
| DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, |
| ActivityThread.currentApplication().getMainExecutor(), |
| mPssDelayConfigListener); |
| mPssDeferralTime = pssDeferralMs; |
| } |
| |
| void onActivityManagerInternalAdded() { |
| mProcessCpuThread.start(); |
| // Wait for the synchronized block started in mProcessCpuThread, |
| // so that any other access to mProcessCpuTracker from main thread |
| // will be blocked during mProcessCpuTracker initialization. |
| try { |
| mProcessCpuInitLatch.await(); |
| } catch (InterruptedException e) { |
| Slog.wtf(TAG, "Interrupted wait during start", e); |
| Thread.currentThread().interrupt(); |
| throw new IllegalStateException("Interrupted wait during start"); |
| } |
| } |
| |
| void onActivityLaunched() { |
| // This is safe to force to the head of the queue because it relies only |
| // on refcounting to track begin/end of deferrals, not on actual |
| // message ordering. We don't care *what* activity is being |
| // launched; only that we're doing so. |
| if (mPssDeferralTime > 0) { |
| final Message msg = mBgHandler.obtainMessage(BgHandler.DEFER_PSS_MSG); |
| mBgHandler.sendMessageAtFrontOfQueue(msg); |
| } |
| } |
| |
| @GuardedBy("mService") |
| ProfilerInfo setupProfilerInfoLocked(@NonNull IApplicationThread thread, ProcessRecord app, |
| ActiveInstrumentation instr) throws IOException, RemoteException { |
| ProfilerInfo profilerInfo = null; |
| String preBindAgent = null; |
| final String processName = app.processName; |
| synchronized (mProfilerLock) { |
| if (mProfileData.getProfileApp() != null |
| && mProfileData.getProfileApp().equals(processName)) { |
| mProfileData.setProfileProc(app); |
| if (mProfileData.getProfilerInfo() != null) { |
| // Send a profiler info object to the app if either a file is given, or |
| // an agent should be loaded at bind-time. |
| boolean needsInfo = mProfileData.getProfilerInfo().profileFile != null |
| || mProfileData.getProfilerInfo().attachAgentDuringBind; |
| profilerInfo = needsInfo |
| ? new ProfilerInfo(mProfileData.getProfilerInfo()) : null; |
| if (mProfileData.getProfilerInfo().agent != null) { |
| preBindAgent = mProfileData.getProfilerInfo().agent; |
| } |
| } |
| } else if (instr != null && instr.mProfileFile != null) { |
| profilerInfo = new ProfilerInfo(instr.mProfileFile, null, 0, false, false, null, |
| false, 0, ProfilerInfo.OUTPUT_VERSION_DEFAULT); |
| } |
| if (mAppAgentMap != null && mAppAgentMap.containsKey(processName)) { |
| // We need to do a debuggable check here. See setAgentApp for why the check is |
| // postponed to here. |
| if (app.isDebuggable()) { |
| String agent = mAppAgentMap.get(processName); |
| // Do not overwrite already requested agent. |
| if (profilerInfo == null) { |
| profilerInfo = new ProfilerInfo(null, null, 0, false, false, |
| mAppAgentMap.get(processName), true, 0, |
| ProfilerInfo.OUTPUT_VERSION_DEFAULT); |
| } else if (profilerInfo.agent == null) { |
| profilerInfo = profilerInfo.setAgent(mAppAgentMap.get(processName), true); |
| } |
| } |
| } |
| |
| if (profilerInfo != null && profilerInfo.profileFd != null) { |
| profilerInfo.profileFd = profilerInfo.profileFd.dup(); |
| if (TextUtils.equals(mProfileData.getProfileApp(), processName) |
| && mProfileData.getProfilerInfo() != null) { |
| clearProfilerLPf(); |
| } |
| } |
| } |
| |
| // Check if this is a secondary process that should be incorporated into some |
| // currently active instrumentation. (Note we do this AFTER all of the profiling |
| // stuff above because profiling can currently happen only in the primary |
| // instrumentation process.) |
| if (mService.mActiveInstrumentation.size() > 0 && instr == null) { |
| for (int i = mService.mActiveInstrumentation.size() - 1; |
| i >= 0 && app.getActiveInstrumentation() == null; i--) { |
| ActiveInstrumentation aInstr = mService.mActiveInstrumentation.get(i); |
| if (!aInstr.mFinished && aInstr.mTargetInfo.uid == app.uid) { |
| synchronized (mProcLock) { |
| if (aInstr.mTargetProcesses.length == 0) { |
| // This is the wildcard mode, where every process brought up for |
| // the target instrumentation should be included. |
| if (aInstr.mTargetInfo.packageName.equals(app.info.packageName)) { |
| app.setActiveInstrumentation(aInstr); |
| aInstr.mRunningProcesses.add(app); |
| } |
| } else { |
| for (String proc : aInstr.mTargetProcesses) { |
| if (proc.equals(app.processName)) { |
| app.setActiveInstrumentation(aInstr); |
| aInstr.mRunningProcesses.add(app); |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // If we were asked to attach an agent on startup, do so now, before we're binding |
| // application code. |
| if (preBindAgent != null) { |
| thread.attachAgent(preBindAgent); |
| } |
| if (app.isDebuggable()) { |
| thread.attachStartupAgents(app.info.dataDir); |
| } |
| return profilerInfo; |
| } |
| |
| @GuardedBy("mService") |
| void onCleanupApplicationRecordLocked(ProcessRecord app) { |
| synchronized (mProfilerLock) { |
| final ProcessProfileRecord profile = app.mProfile; |
| mProcessesToGc.remove(app); |
| mPendingPssOrRssProfiles.remove(profile); |
| profile.abortNextPssTime(); |
| } |
| } |
| |
| @GuardedBy("mService") |
| void onAppDiedLocked(ProcessRecord app) { |
| synchronized (mProfilerLock) { |
| if (mProfileData.getProfileProc() == app) { |
| clearProfilerLPf(); |
| } |
| } |
| } |
| |
| @GuardedBy("mProfilerLock") |
| boolean dumpMemWatchProcessesLPf(PrintWriter pw, boolean needSep) { |
| if (mMemWatchProcesses.getMap().size() > 0) { |
| pw.println(" Mem watch processes:"); |
| final ArrayMap<String, SparseArray<Pair<Long, String>>> procs = |
| mMemWatchProcesses.getMap(); |
| for (int i = procs.size() - 1; i >= 0; i--) { |
| final String proc = procs.keyAt(i); |
| final SparseArray<Pair<Long, String>> uids = procs.valueAt(i); |
| for (int j = uids.size() - 1; j >= 0; j--) { |
| if (needSep) { |
| pw.println(); |
| needSep = false; |
| } |
| StringBuilder sb = new StringBuilder(); |
| sb.append(" ").append(proc).append('/'); |
| UserHandle.formatUid(sb, uids.keyAt(j)); |
| Pair<Long, String> val = uids.valueAt(j); |
| sb.append(": "); DebugUtils.sizeValueToString(val.first, sb); |
| if (val.second != null) { |
| sb.append(", report to ").append(val.second); |
| } |
| pw.println(sb.toString()); |
| } |
| } |
| pw.print(" mMemWatchDumpProcName="); pw.println(mMemWatchDumpProcName); |
| pw.print(" mMemWatchDumpUri="); pw.println(mMemWatchDumpUri); |
| pw.print(" mMemWatchDumpPid="); pw.println(mMemWatchDumpPid); |
| pw.print(" mMemWatchDumpUid="); pw.println(mMemWatchDumpUid); |
| pw.print(" mMemWatchIsUserInitiated="); pw.println(mMemWatchIsUserInitiated); |
| } |
| return needSep; |
| } |
| |
| @GuardedBy("mService") |
| boolean dumpProfileDataLocked(PrintWriter pw, String dumpPackage, boolean needSep) { |
| if (mProfileData.getProfileApp() != null || mProfileData.getProfileProc() != null |
| || (mProfileData.getProfilerInfo() != null |
| && (mProfileData.getProfilerInfo().profileFile != null |
| || mProfileData.getProfilerInfo().profileFd != null))) { |
| if (dumpPackage == null || dumpPackage.equals(mProfileData.getProfileApp())) { |
| if (needSep) { |
| pw.println(); |
| needSep = false; |
| } |
| pw.println(" mProfileApp=" + mProfileData.getProfileApp() |
| + " mProfileProc=" + mProfileData.getProfileProc()); |
| if (mProfileData.getProfilerInfo() != null) { |
| pw.println(" mProfileFile=" + mProfileData.getProfilerInfo().profileFile |
| + " mProfileFd=" + mProfileData.getProfilerInfo().profileFd); |
| pw.println( |
| " mSamplingInterval=" + mProfileData.getProfilerInfo().samplingInterval |
| + " mAutoStopProfiler=" |
| + mProfileData.getProfilerInfo().autoStopProfiler |
| + " mStreamingOutput=" |
| + mProfileData.getProfilerInfo().streamingOutput |
| + " mClockType=" |
| + mProfileData.getProfilerInfo().clockType |
| + " mProfilerOutputVersion=" |
| + mProfileData.getProfilerInfo().profilerOutputVersion); |
| pw.println(" mProfileType=" + mProfileType); |
| } |
| } |
| } |
| return needSep; |
| } |
| |
| @GuardedBy("mService") |
| void dumpLastMemoryLevelLocked(PrintWriter pw) { |
| switch (mLastMemoryLevel) { |
| case ADJ_MEM_FACTOR_NORMAL: |
| pw.println("normal)"); |
| break; |
| case ADJ_MEM_FACTOR_MODERATE: |
| pw.println("moderate)"); |
| break; |
| case ADJ_MEM_FACTOR_LOW: |
| pw.println("low)"); |
| break; |
| case ADJ_MEM_FACTOR_CRITICAL: |
| pw.println("critical)"); |
| break; |
| default: |
| pw.print(mLastMemoryLevel); |
| pw.println(")"); |
| break; |
| } |
| } |
| |
| @GuardedBy("mService") |
| void dumpMemoryLevelsLocked(PrintWriter pw) { |
| pw.println(" mAllowLowerMemLevel=" + mAllowLowerMemLevel |
| + " mLastMemoryLevel=" + mLastMemoryLevel |
| + " mLastNumProcesses=" + mLastNumProcesses); |
| } |
| |
| @GuardedBy("mProfilerLock") |
| void writeMemWatchProcessToProtoLPf(ProtoOutputStream proto) { |
| if (mMemWatchProcesses.getMap().size() > 0) { |
| final long token = proto.start( |
| ActivityManagerServiceDumpProcessesProto.MEM_WATCH_PROCESSES); |
| ArrayMap<String, SparseArray<Pair<Long, String>>> procs = mMemWatchProcesses.getMap(); |
| for (int i = 0; i < procs.size(); i++) { |
| final String proc = procs.keyAt(i); |
| final SparseArray<Pair<Long, String>> uids = procs.valueAt(i); |
| final long ptoken = proto.start( |
| ActivityManagerServiceDumpProcessesProto.MemWatchProcess.PROCS); |
| proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Process.NAME, |
| proc); |
| for (int j = uids.size() - 1; j >= 0; j--) { |
| final long utoken = proto.start(ActivityManagerServiceDumpProcessesProto |
| .MemWatchProcess.Process.MEM_STATS); |
| Pair<Long, String> val = uids.valueAt(j); |
| proto.write(ActivityManagerServiceDumpProcessesProto |
| .MemWatchProcess.Process.MemStats.UID, uids.keyAt(j)); |
| proto.write(ActivityManagerServiceDumpProcessesProto |
| .MemWatchProcess.Process.MemStats.SIZE, |
| DebugUtils.sizeValueToString(val.first, new StringBuilder())); |
| proto.write(ActivityManagerServiceDumpProcessesProto |
| .MemWatchProcess.Process.MemStats.REPORT_TO, val.second); |
| proto.end(utoken); |
| } |
| proto.end(ptoken); |
| } |
| |
| final long dtoken = proto.start( |
| ActivityManagerServiceDumpProcessesProto.MemWatchProcess.DUMP); |
| proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.PROC_NAME, |
| mMemWatchDumpProcName); |
| proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.URI, |
| mMemWatchDumpUri.toString()); |
| proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.PID, |
| mMemWatchDumpPid); |
| proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.UID, |
| mMemWatchDumpUid); |
| proto.write( |
| ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.IS_USER_INITIATED, |
| mMemWatchIsUserInitiated); |
| proto.end(dtoken); |
| |
| proto.end(token); |
| } |
| } |
| |
| @GuardedBy("mService") |
| void writeProfileDataToProtoLocked(ProtoOutputStream proto, String dumpPackage) { |
| if (mProfileData.getProfileApp() != null || mProfileData.getProfileProc() != null |
| || (mProfileData.getProfilerInfo() != null |
| && (mProfileData.getProfilerInfo().profileFile != null |
| || mProfileData.getProfilerInfo().profileFd != null))) { |
| if (dumpPackage == null || dumpPackage.equals(mProfileData.getProfileApp())) { |
| final long token = proto.start(ActivityManagerServiceDumpProcessesProto.PROFILE); |
| proto.write(ActivityManagerServiceDumpProcessesProto.Profile.APP_NAME, |
| mProfileData.getProfileApp()); |
| mProfileData.getProfileProc().dumpDebug(proto, |
| ActivityManagerServiceDumpProcessesProto.Profile.PROC); |
| if (mProfileData.getProfilerInfo() != null) { |
| mProfileData.getProfilerInfo().dumpDebug(proto, |
| ActivityManagerServiceDumpProcessesProto.Profile.INFO); |
| proto.write(ActivityManagerServiceDumpProcessesProto.Profile.TYPE, |
| mProfileType); |
| } |
| proto.end(token); |
| } |
| } |
| } |
| |
| @GuardedBy("mService") |
| void writeMemoryLevelsToProtoLocked(ProtoOutputStream proto) { |
| proto.write(ActivityManagerServiceDumpProcessesProto.ALLOW_LOWER_MEM_LEVEL, |
| mAllowLowerMemLevel); |
| proto.write(ActivityManagerServiceDumpProcessesProto.LAST_MEMORY_LEVEL, mLastMemoryLevel); |
| proto.write(ActivityManagerServiceDumpProcessesProto.LAST_NUM_PROCESSES, mLastNumProcesses); |
| } |
| |
| void printCurrentCpuState(StringBuilder report, long time) { |
| synchronized (mProcessCpuTracker) { |
| // Only print the first 10 processes |
| report.append(mProcessCpuTracker.printCurrentState(time, /* maxProcesses= */10)); |
| } |
| } |
| |
| Pair<String, String> getAppProfileStatsForDebugging(long time, int linesOfStats) { |
| String cpuLoad = null; |
| String stats = null; |
| synchronized (mProcessCpuTracker) { |
| updateCpuStatsNow(); |
| cpuLoad = mProcessCpuTracker.printCurrentLoad(); |
| stats = mProcessCpuTracker.printCurrentState(time); |
| } |
| // Only return linesOfStats lines of Cpu stats. |
| int toIndex = 0; |
| for (int i = 0; i <= linesOfStats; i++) { |
| int nextIndex = stats.indexOf('\n', toIndex); |
| if (nextIndex == -1) { |
| toIndex = stats.length(); |
| break; |
| } |
| toIndex = nextIndex + 1; |
| } |
| return new Pair(cpuLoad, stats.substring(0, toIndex)); |
| } |
| |
| @GuardedBy("mProfilerLock") |
| void writeProcessesToGcToProto(ProtoOutputStream proto, long fieldId, String dumpPackage) { |
| if (mProcessesToGc.size() > 0) { |
| long now = SystemClock.uptimeMillis(); |
| for (int i = 0, size = mProcessesToGc.size(); i < size; i++) { |
| ProcessRecord r = mProcessesToGc.get(i); |
| if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) { |
| continue; |
| } |
| final long token = proto.start(fieldId); |
| final ProcessProfileRecord profile = r.mProfile; |
| r.dumpDebug(proto, ProcessToGcProto.PROC); |
| proto.write(ProcessToGcProto.REPORT_LOW_MEMORY, profile.getReportLowMemory()); |
| proto.write(ProcessToGcProto.NOW_UPTIME_MS, now); |
| proto.write(ProcessToGcProto.LAST_GCED_MS, profile.getLastRequestedGc()); |
| proto.write(ProcessToGcProto.LAST_LOW_MEMORY_MS, profile.getLastLowMemory()); |
| proto.end(token); |
| } |
| } |
| } |
| |
| @GuardedBy("mProfilerLock") |
| boolean dumpProcessesToGc(PrintWriter pw, boolean needSep, String dumpPackage) { |
| if (mProcessesToGc.size() > 0) { |
| boolean printed = false; |
| long now = SystemClock.uptimeMillis(); |
| for (int i = 0, size = mProcessesToGc.size(); i < size; i++) { |
| ProcessRecord proc = mProcessesToGc.get(i); |
| if (dumpPackage != null && !dumpPackage.equals(proc.info.packageName)) { |
| continue; |
| } |
| if (!printed) { |
| if (needSep) pw.println(); |
| needSep = true; |
| pw.println(" Processes that are waiting to GC:"); |
| printed = true; |
| } |
| pw.print(" Process "); pw.println(proc); |
| final ProcessProfileRecord profile = proc.mProfile; |
| pw.print(" lowMem="); pw.print(profile.getReportLowMemory()); |
| pw.print(", last gced="); |
| pw.print(now - profile.getLastRequestedGc()); |
| pw.print(" ms ago, last lowMem="); |
| pw.print(now - profile.getLastLowMemory()); |
| pw.println(" ms ago"); |
| |
| } |
| } |
| return needSep; |
| } |
| } |