Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 15755084 authored by Jing Ji's avatar Jing Ji
Browse files

Track the child processes that are forked by app processes

Apps could use Runtime.exec() to spawn child process and framework
will have no idea about its lifecycle. Now track those processes
whenever we find them - currently during the cpu stats sampling
they could be spotted. If it's consuming too much CPU while its
parent app process are also in the background, kill it.

By default we allow up to 32 such processes; the process with the
worst oom adj score of their parents will be killed if there are
too many of them.

Bug: 160548789
Bug: 157089413
Test: atest AppChildProcessTest
Test: atest CtsAppTestCases:ActivityManagerTest
Change-Id: I95b20a1f36ccd43d09027201d14872305b169d8d
parent 8353fe00
Loading
Loading
Loading
Loading
+61 −23
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import dalvik.system.VMRuntime;
import libcore.io.IoUtils;

import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeoutException;

@@ -1317,34 +1318,17 @@ public class Process {
     */
    public static void waitForProcessDeath(int pid, int timeout)
            throws InterruptedException, TimeoutException {
        FileDescriptor pidfd = null;
        if (sPidFdSupported == PIDFD_UNKNOWN) {
            int fd = -1;
            try {
                fd = nativePidFdOpen(pid, 0);
                sPidFdSupported = PIDFD_SUPPORTED;
            } catch (ErrnoException e) {
                sPidFdSupported = e.errno != OsConstants.ENOSYS
                    ? PIDFD_SUPPORTED : PIDFD_UNSUPPORTED;
            } finally {
                if (fd >= 0) {
                    pidfd = new FileDescriptor();
                    pidfd.setInt$(fd);
                }
            }
        }
        boolean fallback = sPidFdSupported == PIDFD_UNSUPPORTED;
        boolean fallback = supportsPidFd();
        if (!fallback) {
            FileDescriptor pidfd = null;
            try {
                if (pidfd == null) {
                    int fd = nativePidFdOpen(pid, 0);
                final int fd = nativePidFdOpen(pid, 0);
                if (fd >= 0) {
                    pidfd = new FileDescriptor();
                    pidfd.setInt$(fd);
                } else {
                    fallback = true;
                }
                }
                if (pidfd != null) {
                    StructPollfd[] fds = new StructPollfd[] {
                        new StructPollfd()
@@ -1392,5 +1376,59 @@ public class Process {
        throw new TimeoutException();
    }

    /**
     * Determine whether the system supports pidfd APIs
     *
     * @return Returns true if the system supports pidfd APIs
     * @hide
     */
    public static boolean supportsPidFd() {
        if (sPidFdSupported == PIDFD_UNKNOWN) {
            int fd = -1;
            try {
                fd = nativePidFdOpen(myPid(), 0);
                sPidFdSupported = PIDFD_SUPPORTED;
            } catch (ErrnoException e) {
                sPidFdSupported = e.errno != OsConstants.ENOSYS
                        ? PIDFD_SUPPORTED : PIDFD_UNSUPPORTED;
            } finally {
                if (fd >= 0) {
                    final FileDescriptor f = new FileDescriptor();
                    f.setInt$(fd);
                    IoUtils.closeQuietly(f);
                }
            }
        }
        return sPidFdSupported == PIDFD_SUPPORTED;
    }

    /**
     * Open process file descriptor for given pid.
     *
     * @param pid The process ID to open for
     * @param flags Reserved, unused now, must be 0
     * @return The process file descriptor for given pid
     * @throws IOException if it can't be opened
     *
     * @hide
     */
    public static @Nullable FileDescriptor openPidFd(int pid, int flags) throws IOException {
        if (!supportsPidFd()) {
            return null;
        }
        if (flags != 0) {
            throw new IllegalArgumentException();
        }
        try {
            FileDescriptor pidfd = new FileDescriptor();
            pidfd.setInt$(nativePidFdOpen(pid, flags));
            return pidfd;
        } catch (ErrnoException e) {
            IOException ex = new IOException();
            ex.initCause(e);
            throw ex;
        }
    }

    private static native int nativePidFdOpen(int pid, int flags) throws ErrnoException;
}
+36 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
@@ -124,6 +125,7 @@ final class ActivityManagerConstants extends ContentObserver {
    private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
    private static final int DEFAULT_PENDINGINTENT_WARNING_THRESHOLD = 2000;
    private static final int DEFAULT_MIN_CRASH_INTERVAL = 2 * 60 * 1000;
    private static final int DEFAULT_MAX_PHANTOM_PROCESSES = 32;


    // Flag stored in the DeviceConfig API.
@@ -132,6 +134,11 @@ final class ActivityManagerConstants extends ContentObserver {
     */
    private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";

    /**
     * Maximum number of cached processes.
     */
    private static final String KEY_MAX_PHANTOM_PROCESSES = "max_phantom_processes";

    /**
     * Default value for mFlagBackgroundActivityStartsEnabled if not explicitly set in
     * Settings.Global. This allows it to be set experimentally unless it has been
@@ -364,6 +371,11 @@ final class ActivityManagerConstants extends ContentObserver {
     */
    public final ArraySet<ComponentName> KEEP_WARMING_SERVICES = new ArraySet<ComponentName>();

    /**
     * Maximum number of phantom processes.
     */
    public int MAX_PHANTOM_PROCESSES = DEFAULT_MAX_PHANTOM_PROCESSES;

    private List<String> mDefaultImperceptibleKillExemptPackages;
    private List<Integer> mDefaultImperceptibleKillExemptProcStates;

@@ -481,6 +493,9 @@ final class ActivityManagerConstants extends ContentObserver {
                            case KEY_BINDER_HEAVY_HITTER_AUTO_SAMPLER_THRESHOLD:
                                updateBinderHeavyHitterWatcher();
                                break;
                            case KEY_MAX_PHANTOM_PROCESSES:
                                updateMaxPhantomProcesses();
                                break;
                            default:
                                break;
                        }
@@ -599,6 +614,8 @@ final class ActivityManagerConstants extends ContentObserver {
                // with defaults.
                Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e);
            }
            final long currentPowerCheckInterval = POWER_CHECK_INTERVAL;

            BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,
                    DEFAULT_BACKGROUND_SETTLE_TIME);
            FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME,
@@ -664,6 +681,13 @@ final class ActivityManagerConstants extends ContentObserver {
            PENDINGINTENT_WARNING_THRESHOLD = mParser.getInt(KEY_PENDINGINTENT_WARNING_THRESHOLD,
                    DEFAULT_PENDINGINTENT_WARNING_THRESHOLD);

            if (POWER_CHECK_INTERVAL != currentPowerCheckInterval) {
                mService.mHandler.removeMessages(
                        ActivityManagerService.CHECK_EXCESSIVE_POWER_USE_MSG);
                final Message msg = mService.mHandler.obtainMessage(
                        ActivityManagerService.CHECK_EXCESSIVE_POWER_USE_MSG);
                mService.mHandler.sendMessageDelayed(msg, POWER_CHECK_INTERVAL);
            }
            // For new flags that are intended for server-side experiments, please use the new
            // DeviceConfig package.
        }
@@ -811,6 +835,16 @@ final class ActivityManagerConstants extends ContentObserver {
        mService.scheduleUpdateBinderHeavyHitterWatcherConfig();
    }

    private void updateMaxPhantomProcesses() {
        final int oldVal = MAX_PHANTOM_PROCESSES;
        MAX_PHANTOM_PROCESSES = DeviceConfig.getInt(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MAX_PHANTOM_PROCESSES,
                DEFAULT_MAX_PHANTOM_PROCESSES);
        if (oldVal > MAX_PHANTOM_PROCESSES) {
            mService.mHandler.post(mService.mPhantomProcessList::trimPhantomProcessesIfNecessary);
        }
    }

    void dump(PrintWriter pw) {
        pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
                + Settings.Global.ACTIVITY_MANAGER_CONSTANTS + ":");
@@ -897,6 +931,8 @@ final class ActivityManagerConstants extends ContentObserver {
        pw.println(BINDER_HEAVY_HITTER_AUTO_SAMPLER_BATCHSIZE);
        pw.print("  "); pw.print(KEY_BINDER_HEAVY_HITTER_AUTO_SAMPLER_THRESHOLD); pw.print("=");
        pw.println(BINDER_HEAVY_HITTER_AUTO_SAMPLER_THRESHOLD);
        pw.print("  "); pw.print(KEY_MAX_PHANTOM_PROCESSES); pw.print("=");
        pw.println(MAX_PHANTOM_PROCESSES);

        pw.println();
        if (mOverrideMaxCachedProcesses >= 0) {
+87 −45
Original line number Diff line number Diff line
@@ -634,6 +634,12 @@ public class ActivityManagerService extends IActivityManager.Stub
     */
    final ProcessList mProcessList;
    /**
     * The list of phantom processes.
     * @see PhantomProcessRecord
     */
    final PhantomProcessList mPhantomProcessList;
    /**
     * Tracking long-term execution of processes to look for abuse and other
     * bad app behavior.
@@ -1996,6 +2002,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        mProcessList = injector.getProcessList(this);
        mProcessList.init(this, activeUids, mPlatformCompat);
        mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null);
        mPhantomProcessList = new PhantomProcessList(this);
        mOomAdjuster = hasHandlerThread
                ? new OomAdjuster(this, mProcessList, activeUids, handlerThread) : null;
@@ -2053,6 +2060,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        mProcessList.init(this, activeUids, mPlatformCompat);
        mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(),
                new LowMemDetector(this));
        mPhantomProcessList = new PhantomProcessList(this);
        mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
        // Broadcast policy parameters
@@ -9209,6 +9217,10 @@ public class ActivityManagerService extends IActivityManager.Stub
            }
        }
        if (dumpAll) {
            mPhantomProcessList.dump(pw, "  ");
        }
        if (mImportantProcesses.size() > 0) {
            synchronized (mPidsSelfLocked) {
                boolean printed = false;
@@ -14832,29 +14844,8 @@ public class ActivityManagerService extends IActivityManager.Stub
            int i = mProcessList.mLruProcesses.size();
            while (i > 0) {
                i--;
                ProcessRecord app = mProcessList.mLruProcesses.get(i);
                final ProcessRecord app = mProcessList.mLruProcesses.get(i);
                if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
                    if (app.lastCpuTime <= 0) {
                        continue;
                    }
                    long cputimeUsed = app.curCpuTime - app.lastCpuTime;
                    if (DEBUG_POWER) {
                        StringBuilder sb = new StringBuilder(128);
                        sb.append("CPU for ");
                        app.toShortString(sb);
                        sb.append(": over ");
                        TimeUtils.formatDuration(uptimeSince, sb);
                        sb.append(" used ");
                        TimeUtils.formatDuration(cputimeUsed, sb);
                        sb.append(" (");
                        sb.append((cputimeUsed * 100) / uptimeSince);
                        sb.append("%)");
                        Slog.i(TAG_POWER, sb.toString());
                    }
                    // If the process has used too much CPU over the last duration, the
                    // user probably doesn't want this, so kill!
                    if (doCpuKills && uptimeSince > 0) {
                        // What is the limit for this process?
                    int cpuLimit;
                    long checkDur = curUptime - app.getWhenUnimportant();
                    if (checkDur <= mConstants.POWER_CHECK_INTERVAL) {
@@ -14867,9 +14858,10 @@ public class ActivityManagerService extends IActivityManager.Stub
                    } else {
                        cpuLimit = mConstants.POWER_CHECK_MAX_CPU_4;
                    }
                        if (((cputimeUsed * 100) / uptimeSince) >= cpuLimit) {
                            mBatteryStatsService.reportExcessiveCpu(app.info.uid, app.processName,
                                        uptimeSince, cputimeUsed);
                    if (app.lastCpuTime > 0) {
                        final long cputimeUsed = app.curCpuTime - app.lastCpuTime;
                        if (checkExcessivePowerUsageLocked(uptimeSince, doCpuKills, cputimeUsed,
                                app.processName, app.toShortString(), cpuLimit, app)) {
                            app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince
                                    + " dur=" + checkDur + " limit=" + cpuLimit,
                                    ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
@@ -14878,21 +14870,71 @@ public class ActivityManagerService extends IActivityManager.Stub
                            synchronized (mProcessStats.mLock) {
                                app.baseProcessTracker.reportExcessiveCpu(app.pkgList.mPkgList);
                            }
                        }
                    }
                    app.lastCpuTime = app.curCpuTime;
                    // Also check the phantom processes if there is any
                    final long chkDur = checkDur;
                    final int cpuLmt = cpuLimit;
                    final boolean doKill = doCpuKills;
                    mPhantomProcessList.forEachPhantomProcessOfApp(app, r -> {
                        if (r.mLastCputime > 0) {
                            final long cputimeUsed = r.mCurrentCputime - r.mLastCputime;
                            if (checkExcessivePowerUsageLocked(uptimeSince, doKill, cputimeUsed,
                                    app.processName, r.toString(), cpuLimit, app)) {
                                mPhantomProcessList.killPhantomProcessGroupLocked(app, r,
                                        ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
                                        ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
                                        "excessive cpu " + cputimeUsed + " during "
                                        + uptimeSince + " dur=" + chkDur + " limit=" + cpuLmt);
                                return false;
                            }
                        }
                        r.mLastCputime = r.mCurrentCputime;
                        return true;
                    });
                }
            }
        }
    }
    private boolean checkExcessivePowerUsageLocked(final long uptimeSince, boolean doCpuKills,
            final long cputimeUsed, final String processName, final String description,
            final int cpuLimit, final ProcessRecord app) {
        if (DEBUG_POWER) {
            StringBuilder sb = new StringBuilder(128);
            sb.append("CPU for ");
            sb.append(description);
            sb.append(": over ");
            TimeUtils.formatDuration(uptimeSince, sb);
            sb.append(" used ");
            TimeUtils.formatDuration(cputimeUsed, sb);
            sb.append(" (");
            sb.append((cputimeUsed * 100.0) / uptimeSince);
            sb.append("%)");
            Slog.i(TAG_POWER, sb.toString());
        }
        // If the process has used too much CPU over the last duration, the
        // user probably doesn't want this, so kill!
        if (doCpuKills && uptimeSince > 0) {
            if (((cputimeUsed * 100) / uptimeSince) >= cpuLimit) {
                mBatteryStatsService.reportExcessiveCpu(app.info.uid, app.processName,
                        uptimeSince, cputimeUsed);
                for (int ipkg = app.pkgList.size() - 1; ipkg >= 0; ipkg--) {
                    ProcessStats.ProcessStateHolder holder = app.pkgList.valueAt(ipkg);
                    FrameworkStatsLog.write(
                            FrameworkStatsLog.EXCESSIVE_CPU_USAGE_REPORTED,
                            app.info.uid,
                                        holder.state.getName(),
                            processName,
                            holder.state.getPackage(),
                            holder.appVersion);
                }
                return true;
            }
        }
                    app.lastCpuTime = app.curCpuTime;
                }
            }
        }
        return false;
    }
    final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
+4 −0
Original line number Diff line number Diff line
@@ -1257,6 +1257,10 @@ public class AppProfiler {
                }
            }

            if (haveNewCpuStats) {
                mService.mPhantomProcessList.updateProcessCpuStatesLocked(mProcessCpuTracker);
            }

            final BatteryStatsImpl bstats = mService.mBatteryStatsService.getActiveStatistics();
            synchronized (bstats) {
                if (haveNewCpuStats) {
+395 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading