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

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

Support time-in-state proc stats for UIDs

Bug: 200326767
Test: atest ProcStatsValidationTests
Test: atest ProcessStatsDumpsysTest
Test: Manual - dumpsys procstats -a
Change-Id: Ic2da178c244706bb43488e57fb808a77225bad6d
parent b3691198
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -473,6 +473,7 @@ public final class ProcessState {
                }
            }
            mCurCombinedState = state;
            mStats.mUidStates.get(mUid).updateCombinedState(state, now);
        }
    }

+112 −5
Original line number Diff line number Diff line
@@ -179,17 +179,19 @@ public final class ProcessStats implements Parcelable {
    public static final int REPORT_PKG_ASC_STATS = 0x08;
    // Should report package stats.
    public static final int REPORT_PKG_STATS = 0x0E;
    // Should report uid stats.
    public static final int REPORT_UID_STATS = 0x10;
    // Should report all stats.
    public static final int REPORT_ALL = 0x0F;
    public static final int REPORT_ALL = 0x1F;

    public static final int[] OPTIONS =
            {REPORT_PROC_STATS, REPORT_PKG_PROC_STATS, REPORT_PKG_SVC_STATS, REPORT_PKG_ASC_STATS,
                    REPORT_PKG_STATS, REPORT_ALL};
                    REPORT_PKG_STATS, REPORT_UID_STATS, REPORT_ALL};
    public static final String[] OPTIONS_STR =
            {"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "all"};
            {"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "uid", "all"};

    // Current version of the parcel format.
    private static final int PARCEL_VERSION = 40;
    private static final int PARCEL_VERSION = 41;
    // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
    private static final int MAGIC = 0x50535454;

@@ -200,6 +202,8 @@ public final class ProcessStats implements Parcelable {
    public final ProcessMap<LongSparseArray<PackageState>> mPackages = new ProcessMap<>();
    public final ProcessMap<ProcessState> mProcesses = new ProcessMap<>();

    public final SparseArray<UidState> mUidStates = new SparseArray<>();

    public final ArrayList<AssociationState.SourceState> mTrackingAssociations = new ArrayList<>();

    public final long[] mMemFactorDurations = new long[ADJ_COUNT];
@@ -337,6 +341,18 @@ public final class ProcessStats implements Parcelable {
            }
        }

        SparseArray<UidState> uidStates = other.mUidStates;
        for (int ip = 0, size = uidStates.size(); ip < size; ip++) {
            final int uid = uidStates.keyAt(ip);
            UidState uidState = mUidStates.get(uid);
            if (uidState == null) {
                uidState = uidStates.valueAt(ip).clone();
                mUidStates.put(uid, uidState);
            } else {
                uidState.add(uidStates.valueAt(ip));
            }
        }

        ArrayMap<String, SparseArray<ProcessState>> procMap = other.mProcesses.getMap();
        for (int ip=0; ip<procMap.size(); ip++) {
            SparseArray<ProcessState> uids = procMap.valueAt(ip);
@@ -358,8 +374,20 @@ public final class ProcessStats implements Parcelable {
                    }
                }
                thisProc.add(otherProc);
                if (thisProc.isActive()) {
                    UidState uidState = mUidStates.get(uid);
                    if (uidState == null) {
                        uidState = new UidState(this, uid);
                        mUidStates.put(uid, uidState);
                    }
                    uidState.addProcess(thisProc);
                }
            }
        }

        for (int ip = 0, size = mUidStates.size(); ip < size; ip++) {
            mUidStates.valueAt(ip).updateCombinedState(-1);
        }

        for (int i=0; i<ADJ_COUNT; i++) {
            if (DEBUG) Slog.d(TAG, "Total duration #" + i + " inc by "
@@ -488,6 +516,7 @@ public final class ProcessStats implements Parcelable {
        resetCommon();
        mPackages.getMap().clear();
        mProcesses.getMap().clear();
        mUidStates.clear();
        mMemFactor = STATE_NOTHING;
        mStartTime = 0;
        if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr);
@@ -580,6 +609,7 @@ public final class ProcessStats implements Parcelable {
                    }
                } else {
                    ps.makeDead();
                    mUidStates.get(uids.keyAt(iu)).removeProcess(ps, now);
                    uids.removeAt(iu);
                }
            }
@@ -588,6 +618,15 @@ public final class ProcessStats implements Parcelable {
            }
        }

        for (int ip = mUidStates.size() - 1; ip >= 0; ip--) {
            final UidState uidState = mUidStates.valueAt(ip);
            if (uidState.isInUse()) {
                mUidStates.valueAt(ip).resetSafely(now);
            } else {
                mUidStates.removeAt(ip);
            }
        }

        mStartTime = now;
        if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr);
    }
@@ -900,6 +939,13 @@ public final class ProcessStats implements Parcelable {

        mSysMemUsage.writeToParcel(out);

        final int numOfUids = mUidStates.size();
        out.writeInt(numOfUids);
        for (int ip = 0; ip < numOfUids; ip++) {
            out.writeInt(mUidStates.keyAt(ip));
            mUidStates.valueAt(ip).writeToParcel(out, now);
        }

        out.writeInt(NPROC);
        for (int ip=0; ip<NPROC; ip++) {
            writeCommonString(out, procMap.keyAt(ip));
@@ -1026,7 +1072,8 @@ public final class ProcessStats implements Parcelable {

    public void readFromParcel(Parcel in) {
        final boolean hadData = mPackages.getMap().size() > 0
                || mProcesses.getMap().size() > 0;
                || mProcesses.getMap().size() > 0
                || mUidStates.size() > 0;
        if (hadData) {
            resetSafely();
        }
@@ -1083,6 +1130,16 @@ public final class ProcessStats implements Parcelable {
            return;
        }

        final int numOfUids = in.readInt();
        for (int ip = 0; ip < numOfUids; ip++) {
            final int uid = in.readInt();
            final UidState uidState = new UidState(this, uid);
            if (!uidState.readFromParcel(in)) {
                return;
            }
            mUidStates.put(uid, uidState);
        }

        int NPROC = in.readInt();
        if (NPROC < 0) {
            mReadError = "bad process count: " + NPROC;
@@ -1127,9 +1184,17 @@ public final class ProcessStats implements Parcelable {
                if (DEBUG_PARCEL) Slog.d(TAG, "Adding process: " + procName + " " + uid
                        + " " + proc);
                mProcesses.put(procName, uid, proc);

                if (proc.isActive()) {
                    mUidStates.get(uid).addProcess(proc);
                }
            }
        }

        for (int ip = 0; ip < numOfUids; ip++) {
            mUidStates.valueAt(ip).updateCombinedState(-1);
        }

        if (DEBUG_PARCEL) Slog.d(TAG, "Read " + mProcesses.getMap().size() + " processes");

        int NPKG = in.readInt();
@@ -1327,6 +1392,12 @@ public final class ProcessStats implements Parcelable {
            commonProc = new ProcessState(this, pkgState.mPackageName, pkgState.mUid,
                    pkgState.mVersionCode, processName);
            mProcesses.put(processName, pkgState.mUid, commonProc);
            UidState uidState = mUidStates.get(pkgState.mUid);
            if (uidState == null) {
                uidState = new UidState(this, pkgState.mUid);
                mUidStates.put(pkgState.mUid, uidState);
            }
            uidState.addProcess(commonProc);
            if (DEBUG) Slog.d(TAG, "GETPROC created new common " + commonProc);
        }
        if (!commonProc.isMultiPackage()) {
@@ -1787,6 +1858,42 @@ public final class ProcessStats implements Parcelable {
            pw.print(" shown of "); pw.print(numTotalProcs); pw.println(" total");
        }

        if ((section & REPORT_UID_STATS) != 0) {
            SparseArray<UidState> uidStates = mUidStates;
            int numShownUids = 0, numTotalUids = 0;
            printedHeader = false;
            for (int iu = 0, size = uidStates.size(); iu < size; iu++) {
                final int uid = uidStates.keyAt(iu);
                final UidState uidState = uidStates.valueAt(iu);
                numTotalUids++;
                if (reqPackage != null && !uidState.hasPackage(reqPackage)) {
                    continue;
                }
                numShownUids++;
                pw.println();
                if (!printedHeader) {
                    pw.println("Per-UID Stats:");
                    printedHeader = true;
                }
                if (activeOnly && !uidState.isInUse()) {
                    pw.print("      (Not active: ");
                    pw.print(UserHandle.formatUid(uid));
                    pw.println(")");
                    continue;
                }
                pw.print("  * ");
                UserHandle.formatUid(pw, uid);
                pw.print(" (");
                pw.print(uidState.getDurationsBucketCount());
                pw.print(" entries)");
                pw.println(":");
                uidState.dumpState(pw, "        ", ALL_SCREEN_ADJ, ALL_MEM_ADJ,
                        ALL_PROC_STATES, now);
            }
            pw.print("  Total UIDs: "); pw.print(numShownUids);
            pw.print(" shown of "); pw.print(numTotalUids); pw.println(" total");
        }

        if (dumpAll) {
            pw.println();
            if (mTrackingAssociations.size() > 0) {
+31 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.internal.app.procstats;

import android.util.SparseArray;

/**
 * The internal interface to access the process stats service, to be used within
 * system_server only.
 */
public abstract class ProcessStatsInternal {
    /**
     * Return the duration over the given time, that an UID spent in each processs state
     * which is defined in the {@link ProcessStats}, the key of the array is the uid.
     */
    public abstract SparseArray<long[]> getUidProcStateStatsOverTime(long minTime);
}
+315 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.internal.app.procstats;

import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
import static com.android.internal.app.procstats.ProcessStats.STATE_NOTHING;

import android.os.Parcel;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.TimeUtils;

import java.io.PrintWriter;

/**
 * The class to track the individual time-in-state of a UID.
 */
public final class UidState {
    private static final String TAG = "ProcessStats";

    private final ProcessStats mStats;
    private final int mUid;
    private final DurationsTable mDurations;

    private ArraySet<ProcessState> mProcesses = new ArraySet<>();
    private int mCurCombinedState = STATE_NOTHING;
    private long mStartTime;

    private long mTotalRunningStartTime;
    private long mTotalRunningDuration;

    /**
     * Create a new UID state. The initial state is not running.
     */
    public UidState(ProcessStats processStats, int uid) {
        mStats = processStats;
        mUid = uid;
        mDurations = new DurationsTable(processStats.mTableData);
    }

    /**
     * Create a copy of this instance.
     */
    public UidState clone() {
        UidState unew = new UidState(mStats, mUid);
        unew.mDurations.addDurations(mDurations);
        unew.mCurCombinedState = mCurCombinedState;
        unew.mStartTime = mStartTime;
        unew.mTotalRunningStartTime = mTotalRunningStartTime;
        unew.mTotalRunningDuration = mTotalRunningDuration;
        return unew;
    }

    /**
     * Update the current state of the UID, it should be a combination
     * of all running processes in this UID.
     */
    public void updateCombinedState(int state, long now) {
        if (mCurCombinedState != state) {
            updateCombinedState(now);
        }
    }

    /**
     * Update the current state of the UID, it should be a combination
     * of all running processes in this UID.
     */
    public void updateCombinedState(long now) {
        setCombinedStateInner(calcCombinedState(), now);
    }

    private int calcCombinedState() {
        int minCombined = STATE_NOTHING;
        int min = STATE_NOTHING;
        for (int i = 0, size = mProcesses.size(); i < size; i++) {
            final int combinedState = mProcesses.valueAt(i).getCombinedState();
            final int state = combinedState % STATE_COUNT;
            if (combinedState != STATE_NOTHING) {
                if (min == STATE_NOTHING || state < min) {
                    minCombined = combinedState;
                    min = state;
                }
            }
        }
        return minCombined;
    }

    /**
     * Set the combined state and commit the state.
     *
     * @param now When it's negative, the previous state won't be committed.
     */
    private void setCombinedStateInner(int state, long now) {
        if (mCurCombinedState != state) {
            if (now >= 0) {
                commitStateTime(now);
                if (state == STATE_NOTHING) {
                    // We are transitioning to a no longer running state... stop counting run time.
                    mTotalRunningDuration += now - mTotalRunningStartTime;
                } else if (mCurCombinedState == STATE_NOTHING) {
                    // We previously weren't running...  now starting again, clear out total
                    // running info.
                    mTotalRunningDuration = 0;
                }
            }
            mCurCombinedState = state;
        }
    }

    /**
     * @return The current combine state of the UID.
     */
    public int getCombinedState() {
        return mCurCombinedState;
    }

    /**
     * Commit the current state's duration into stats.
     */
    public void commitStateTime(long now) {
        if (mCurCombinedState != STATE_NOTHING) {
            long dur = now - mStartTime;
            if (dur > 0) {
                mDurations.addDuration(mCurCombinedState, dur);
            }
            mTotalRunningDuration += now - mTotalRunningStartTime;
            mTotalRunningStartTime = now;
        }
        mStartTime = now;
    }

    /**
     * Reset the UID stats safely.
     */
    public void resetSafely(long now) {
        mDurations.resetTable();
        mStartTime = now;
    }

    /**
     * @return Whether this UID stats is still being used or not.
     */
    public boolean isInUse() {
        for (int i = 0, size = mProcesses.size(); i < size; i++) {
            if (mProcesses.valueAt(i).isInUse()) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return Whether the given package belongs to this UID or not.
     */
    public boolean hasPackage(String packageName) {
        for (int i = 0, size = mProcesses.size(); i < size; i++) {
            final ProcessState proc = mProcesses.valueAt(i);
            if (TextUtils.equals(packageName, proc.getName())
                    && TextUtils.equals(packageName, proc.getPackage())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Add stats data from another instance to this one.
     */
    public void add(UidState other) {
        mDurations.addDurations(other.mDurations);
        mTotalRunningDuration += other.mTotalRunningDuration;
    }

    void addProcess(ProcessState proc) {
        mProcesses.add(proc);
    }

    void addProcess(ProcessState proc, long now) {
        mProcesses.add(proc);
        setCombinedStateInner(proc.getCombinedState(), now);
    }

    void removeProcess(ProcessState proc, long now) {
        mProcesses.remove(proc);
        setCombinedStateInner(proc.getCombinedState(), now);
    }

    /**
     * @return The total amount of stats it's currently tracking.
     */
    public int getDurationsBucketCount() {
        return mDurations.getKeyCount();
    }

    /**
     * @return The total running duration of this UID.
     */
    public long getTotalRunningDuration(long now) {
        return mTotalRunningDuration
                + (mTotalRunningStartTime != 0 ? (now - mTotalRunningStartTime) : 0);
    }

    /**
     * @return The duration in the given state.
     */
    public long getDuration(int state, long now) {
        long time = mDurations.getValueForId((byte) state);
        if (mCurCombinedState == state) {
            time += now - mStartTime;
        }
        return time;
    }

    /**
     * @return The durations in each process state, the mem/screen factors
     *         are consolidated into the bucket with the same process state.
     */
    public long[] getAggregatedDurationsInStates() {
        final long[] states = new long[STATE_COUNT];
        final int numOfBuckets = getDurationsBucketCount();
        for (int i = 0; i < numOfBuckets; i++) {
            final int key = mDurations.getKeyAt(i);
            final int combinedState = SparseMappingTable.getIdFromKey(key);
            states[combinedState % STATE_COUNT] += mDurations.getValue(key);
        }
        return states;
    }

    void writeToParcel(Parcel out, long now) {
        mDurations.writeToParcel(out);
        out.writeLong(getTotalRunningDuration(now));
    }

    boolean readFromParcel(Parcel in) {
        if (!mDurations.readFromParcel(in)) {
            return false;
        }
        mTotalRunningDuration = in.readLong();
        return true;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(128);
        sb.append("UidState{").append(Integer.toHexString(System.identityHashCode(this)))
                .append(" ").append(UserHandle.formatUid(mUid)).append("}");
        return sb.toString();
    }

    void dumpState(PrintWriter pw, String prefix,
            int[] screenStates, int[] memStates, int[] procStates, long now) {
        long totalTime = 0;
        int printedScreen = -1;
        for (int is = 0; is < screenStates.length; is++) {
            int printedMem = -1;
            for (int im = 0; im < memStates.length; im++) {
                for (int ip = 0; ip < procStates.length; ip++) {
                    final int iscreen = screenStates[is];
                    final int imem = memStates[im];
                    final int bucket = ((iscreen + imem) * STATE_COUNT) + procStates[ip];
                    long time = mDurations.getValueForId((byte) bucket);
                    String running = "";
                    if (mCurCombinedState == bucket) {
                        running = " (running)";
                        time += now - mStartTime;
                    }
                    if (time != 0) {
                        pw.print(prefix);
                        if (screenStates.length > 1) {
                            DumpUtils.printScreenLabel(pw, printedScreen != iscreen
                                    ? iscreen : STATE_NOTHING);
                            printedScreen = iscreen;
                        }
                        if (memStates.length > 1) {
                            DumpUtils.printMemLabel(pw,
                                    printedMem != imem ? imem : STATE_NOTHING, '/');
                            printedMem = imem;
                        }
                        pw.print(DumpUtils.STATE_LABELS[procStates[ip]]); pw.print(": ");
                        TimeUtils.formatDuration(time, pw); pw.println(running);
                        totalTime += time;
                    }
                }
            }
        }
        if (totalTime != 0) {
            pw.print(prefix);
            if (screenStates.length > 1) {
                DumpUtils.printScreenLabel(pw, STATE_NOTHING);
            }
            if (memStates.length > 1) {
                DumpUtils.printMemLabel(pw, STATE_NOTHING, '/');
            }
            pw.print(DumpUtils.STATE_LABEL_TOTAL);
            pw.print(": ");
            TimeUtils.formatDuration(totalTime, pw);
            pw.println();
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -2397,6 +2397,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        CriticalEventLog.init();
        mBatteryStatsService.publish();
        mAppOpsService.publish();
        mProcessStats.publish();
        Slog.d("AppOps", "AppOpsService published");
        LocalServices.addService(ActivityManagerInternal.class, mInternal);
        LocalManagerRegistry.addManager(ActivityManagerLocal.class,
Loading