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

Commit 276ff21a authored by Jing Ji's avatar Jing Ji
Browse files

Support aggregation over association sources per process

Process state association tracks the state via the bindings between
the source and the destination processes, there could be overlaps
from the timeline wise over these bindings, hence the exact duration
that a source process binds to a destination process is unknown.

Now add the support of this, by tracking the association sources per
process, the state changes of these sources will be driven by the
changes from individual source state per association.

The output of 'procstats dump -a; will include a new section
"Aggregated Association Sources" per process.An example:

  * com.android.providers.contacts / u0a122 / v30:
      Process android.process.acore (multi, 2 entries):
        [......]
        Aggregated Association Sources:
          <- com.android.bluetooth/1002 (com.android.bluetooth):
               Active count 271 (ImpFg): +1m0s849ms / 9.2%

Also updated the logic of pulling the procstats data from stastd to
use this new data structure.

Bug: 183101565
Bug: 186438656
Test: atest ProcStatsValidationTests
Test: atest ProcessStatsDumpsysTest
Test: atest CtsIncidentHostTestCases:ProcStatsProtoTest
Test: atest CtsStatsdHostTestCases
Test: Manual - compare statsd proto dump vs. dumpsys procstat -a
Change-Id: I9bf9ba7565761ae3d42046ed4886c8d17f6c18b3
parent d7d47480
Loading
Loading
Loading
Loading
+332 −159

File changed.

Preview size limit exceeded, changes collapsed.

+84 −3
Original line number Diff line number Diff line
@@ -63,6 +63,8 @@ import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;

import com.android.internal.app.ProcessMap;
import com.android.internal.app.procstats.AssociationState.SourceKey;
import com.android.internal.app.procstats.AssociationState.SourceState;
import com.android.internal.app.procstats.ProcessStats.PackageState;
import com.android.internal.app.procstats.ProcessStats.ProcessStateHolder;
import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection;
@@ -162,6 +164,11 @@ public final class ProcessState {
    // Set in computeProcessTimeLocked and used by COMPARATOR to sort. Be careful.
    private long mTmpTotalTime;

    /**
     * The combined source states which has or had an association with this process.
     */
    ArrayMap<SourceKey, SourceState> mCommonSources;

    /**
     * Create a new top-level process state, for the initial case where there is only
     * a single package running in a process.  The initial state is not running.
@@ -267,6 +274,21 @@ public final class ProcessState {
            addCachedKill(other.mNumCachedKill, other.mMinCachedKillPss,
                    other.mAvgCachedKillPss, other.mMaxCachedKillPss);
        }
        if (other.mCommonSources != null) {
            if (mCommonSources == null) {
                mCommonSources = new ArrayMap<>();
            }
            int size = other.mCommonSources.size();
            for (int i = 0; i < size; i++) {
                final SourceKey key = other.mCommonSources.keyAt(i);
                SourceState state = mCommonSources.get(key);
                if (state == null) {
                    state = new SourceState(mStats, null, this, key);
                    mCommonSources.put(key, state);
                }
                state.add(other.mCommonSources.valueAt(i));
            }
        }
    }

    public void resetSafely(long now) {
@@ -278,6 +300,17 @@ public final class ProcessState {
        mNumExcessiveCpu = 0;
        mNumCachedKill = 0;
        mMinCachedKillPss = mAvgCachedKillPss = mMaxCachedKillPss = 0;
        // Reset the combine source state.
        if (mCommonSources != null) {
            for (int ip = mCommonSources.size() - 1; ip >= 0; ip--) {
                final SourceState state = mCommonSources.valueAt(ip);
                if (state.isInUse()) {
                    state.resetSafely(now);
                } else {
                    mCommonSources.removeAt(ip);
                }
            }
        }
    }

    public void makeDead() {
@@ -308,9 +341,18 @@ public final class ProcessState {
            out.writeLong(mAvgCachedKillPss);
            out.writeLong(mMaxCachedKillPss);
        }
        // The combined source state of all associations.
        final int numOfSources = mCommonSources != null ? mCommonSources.size() : 0;
        out.writeInt(numOfSources);
        for (int i = 0; i < numOfSources; i++) {
            final SourceKey key = mCommonSources.keyAt(i);
            final SourceState src = mCommonSources.valueAt(i);
            key.writeToParcel(mStats, out);
            src.writeToParcel(out, 0);
        }
    }

    public boolean readFromParcel(Parcel in, boolean fully) {
    boolean readFromParcel(Parcel in, int version, boolean fully) {
        boolean multiPackage = in.readInt() != 0;
        if (fully) {
            mMultiPackage = multiPackage;
@@ -337,6 +379,19 @@ public final class ProcessState {
        } else {
            mMinCachedKillPss = mAvgCachedKillPss = mMaxCachedKillPss = 0;
        }

        // The combined source state of all associations.
        final int numOfSources = in.readInt();
        if (numOfSources > 0) {
            mCommonSources = new ArrayMap<>(numOfSources);
            for (int i = 0; i < numOfSources; i++) {
                final SourceKey key = new SourceKey(mStats, in, version);
                final SourceState src = new SourceState(mStats, null, this, key);
                src.readFromParcel(in);
                mCommonSources.put(key, src);
            }
        }

        return true;
    }

@@ -433,6 +488,12 @@ public final class ProcessState {
            mTotalRunningStartTime = now;
        }
        mStartTime = now;
        if (mCommonSources != null) {
            for (int ip = mCommonSources.size() - 1; ip >= 0; ip--) {
                final SourceState src = mCommonSources.valueAt(ip);
                src.commitStateTime(now);
            }
        }
    }

    public void incActiveServices(String serviceName) {
@@ -722,6 +783,18 @@ public final class ProcessState {
        return mPssTable.getValueForId((byte)state, PSS_RSS_MAXIMUM);
    }

    SourceState getOrCreateSourceState(SourceKey key) {
        if (mCommonSources == null) {
            mCommonSources = new ArrayMap<>();
        }
        SourceState state = mCommonSources.get(key);
        if (state == null) {
            state = new SourceState(mStats, null, this, key);
            mCommonSources.put(key, state);
        }
        return state;
    }

    /**
     * Sums up the PSS data and adds it to 'data'.
     *
@@ -1038,7 +1111,8 @@ public final class ProcessState {
        }
    }

    public void dumpInternalLocked(PrintWriter pw, String prefix, boolean dumpAll) {
    void dumpInternalLocked(PrintWriter pw, String prefix, String reqPackage,
            long totalTime, long now, boolean dumpAll) {
        if (dumpAll) {
            pw.print(prefix); pw.print("myID=");
                    pw.print(Integer.toHexString(System.identityHashCode(this)));
@@ -1053,6 +1127,13 @@ public final class ProcessState {
                        pw.print("/"); pw.print(mCommonProcess.mUid);
                        pw.print(" pkg="); pw.println(mCommonProcess.mPackage);
            }
            if (mCommonSources != null) {
                pw.print(prefix); pw.println("Aggregated Association Sources:");
                AssociationState.dumpSources(
                        pw, prefix + "  ", prefix + "    ", prefix + "        ",
                        AssociationState.createSortedAssociations(now, totalTime, mCommonSources),
                        now, totalTime, reqPackage, true, dumpAll);
            }
        }
        if (mActive) {
            pw.print(prefix); pw.print("mActive="); pw.println(mActive);
@@ -1559,7 +1640,7 @@ public final class ProcessState {
        }

        mStats.dumpFilteredAssociationStatesProtoForProc(proto, ProcessStatsProto.ASSOCS,
                now, this, procToPkgMap, uidToPkgMap);
                now, this, uidToPkgMap);
        proto.end(token);
    }
}
+51 −85
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ import android.service.procstats.ProcessStatsAssociationProto;
import android.service.procstats.ProcessStatsAvailablePagesProto;
import android.service.procstats.ProcessStatsPackageProto;
import android.service.procstats.ProcessStatsSectionProto;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -187,7 +186,7 @@ public final class ProcessStats implements Parcelable {
            {"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "all"};

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

@@ -1113,12 +1112,12 @@ public final class ProcessStats implements Parcelable {
                final long vers = in.readLong();
                ProcessState proc = hadData ? mProcesses.get(procName, uid) : null;
                if (proc != null) {
                    if (!proc.readFromParcel(in, false)) {
                    if (!proc.readFromParcel(in, version, false)) {
                        return;
                    }
                } else {
                    proc = new ProcessState(this, pkgName, uid, vers, procName);
                    if (!proc.readFromParcel(in, true)) {
                    if (!proc.readFromParcel(in, version, true)) {
                        return;
                    }
                }
@@ -1198,13 +1197,13 @@ public final class ProcessStats implements Parcelable {
                            // they will find and use it from the global procs.
                            ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null;
                            if (proc != null) {
                                if (!proc.readFromParcel(in, false)) {
                                if (!proc.readFromParcel(in, version, false)) {
                                    return;
                                }
                            } else {
                                proc = new ProcessState(commonProc, pkgName, uid, vers, procName,
                                        0);
                                if (!proc.readFromParcel(in, true)) {
                                if (!proc.readFromParcel(in, version, true)) {
                                    return;
                                }
                            }
@@ -1439,16 +1438,15 @@ public final class ProcessStats implements Parcelable {
        final int NUM = mTrackingAssociations.size();
        for (int i = NUM - 1; i >= 0; i--) {
            final AssociationState.SourceState act = mTrackingAssociations.get(i);
            if (act.mProcStateSeq != curSeq || act.mProcState >= ProcessStats.STATE_HOME) {
                // If this association did not get touched the last time we computed
                // process states, or its state ended up down in cached, then we no
                // longer have a reason to track it at all.
                act.stopActive(now);
                act.mInTrackingList = false;
                act.mProcState = ProcessStats.STATE_NOTHING;
            if (act.stopActiveIfNecessary(curSeq, now)) {
                mTrackingAssociations.remove(i);
            } else {
                final ProcessState proc = act.getAssociationState().getProcess();
                final AssociationState asc = act.getAssociationState();
                if (asc == null) {
                    Slog.wtf(TAG, act.toString() + " shouldn't be in the tracking list.");
                    continue;
                }
                final ProcessState proc = asc.getProcess();
                if (proc != null) {
                    final int procState = proc.getCombinedState() % STATE_COUNT;
                    if (act.mProcState == procState) {
@@ -1476,7 +1474,7 @@ public final class ProcessStats implements Parcelable {
                } else {
                    // Don't need rate limiting on it.
                    Slog.wtf(TAG, "Tracking association without process: " + act
                            + " in " + act.getAssociationState());
                            + " in " + asc);
                }
            }
        }
@@ -1640,7 +1638,8 @@ public final class ProcessStats implements Parcelable {
                                            ALL_PROC_STATES, now);
                                    proc.dumpPss(pw, "        ", ALL_SCREEN_ADJ, ALL_MEM_ADJ,
                                            ALL_PROC_STATES, now);
                                    proc.dumpInternalLocked(pw, "        ", dumpAll);
                                    proc.dumpInternalLocked(pw, "        ", reqPackage,
                                            totalTime, now, dumpAll);
                                }
                            } else {
                                ArrayList<ProcessState> procs = new ArrayList<ProcessState>();
@@ -1696,7 +1695,8 @@ public final class ProcessStats implements Parcelable {
                                }
                                final AssociationDumpContainer cont =
                                        new AssociationDumpContainer(asc);
                                cont.mSources = asc.createSortedAssociations(now, totalTime);
                                cont.mSources = AssociationState
                                        .createSortedAssociations(now, totalTime, asc.mSources);
                                cont.mTotalTime = asc.getTotalDuration(now);
                                cont.mActiveTime = asc.getActiveDuration(now);
                                associations.add(cont);
@@ -1777,7 +1777,7 @@ public final class ProcessStats implements Parcelable {
                    proc.dumpProcessState(pw, "        ", ALL_SCREEN_ADJ, ALL_MEM_ADJ,
                            ALL_PROC_STATES, now);
                    proc.dumpPss(pw, "        ", ALL_SCREEN_ADJ, ALL_MEM_ADJ, ALL_PROC_STATES, now);
                    proc.dumpInternalLocked(pw, "        ", dumpAll);
                    proc.dumpInternalLocked(pw, "        ", reqPackage, totalTime, now, dumpAll);
                }
            }
            pw.print("  Total procs: "); pw.print(numShownProcs);
@@ -1792,6 +1792,10 @@ public final class ProcessStats implements Parcelable {
                for (int i = 0; i < mTrackingAssociations.size(); i++) {
                    final AssociationState.SourceState src = mTrackingAssociations.get(i);
                    final AssociationState asc = src.getAssociationState();
                    if (asc == null) {
                        Slog.wtf(TAG, src.toString() + " shouldn't be in the tracking list.");
                        continue;
                    }
                    pw.print("  #");
                    pw.print(i);
                    pw.print(": ");
@@ -2353,74 +2357,32 @@ public final class ProcessStats implements Parcelable {
     * @param fieldId   The proto output field ID
     * @param now       The timestamp when the dump was initiated.
     * @param procState The target process where its association states should be dumped.
     * @param proc2Pkg  The map between process to packages running within it.
     * @param uidToPkgMap The map between UID to packages with this UID
     */
    public void dumpFilteredAssociationStatesProtoForProc(ProtoOutputStream proto,
            long fieldId, long now, ProcessState procState,
            final ProcessMap<ArraySet<PackageState>> proc2Pkg,
            final SparseArray<ArraySet<String>> uidToPkgMap) {
        if (procState.isMultiPackage() && procState.getCommonProcess() != procState) {
            // It's a per-package process state, don't bother to write into statsd
            return;
        }
        ArrayMap<SourceKey, long[]> assocVals = new ArrayMap<>();
        final String procName = procState.getName();
        final int procUid = procState.getUid();
        final long procVersion = procState.getVersion();
        final ArraySet<PackageState> packages = proc2Pkg.get(procName, procUid);
        if (packages == null || packages.isEmpty()) {
            // Shouldn't happen
            return;
        }
        for (int i = packages.size() - 1; i >= 0; i--) {
            final PackageState pkgState = packages.valueAt(i);
            final ArrayMap<String, AssociationState> associations = pkgState.mAssociations;
            for (int j = associations.size() - 1; j >= 0; j--) {
                final AssociationState assoc = associations.valueAt(j);
                // Make sure this association is really about this process
                if (!TextUtils.equals(assoc.getProcessName(), procName)) {
                    continue;
                }
                final ArrayMap<SourceKey, SourceState> sources = assoc.mSources;
                for (int k = sources.size() - 1; k >= 0; k--) {
                    final SourceKey key = sources.keyAt(k);
                    final SourceState state = sources.valueAt(k);
                    long[] vals = assocVals.get(key);
                    if (vals == null) {
                        vals = new long[2];
                        assocVals.put(key, vals);
                    }
                    vals[0] += state.mDuration;
                    vals[1] += state.mCount;
                    if (state.mNesting > 0) {
                        vals[0] += now - state.mStartUptime;
                    }
                }
            }
        }
        final ArrayMap<SourceKey, SourceState> sources = procState.mCommonSources;
        if (sources != null && !sources.isEmpty()) {
            final IProcessStats procStatsService = IProcessStats.Stub.asInterface(
                    ServiceManager.getService(SERVICE_NAME));
            if (procStatsService != null) {
                try {
                    final long minimum = procStatsService.getMinAssociationDumpDuration();
                if (minimum > 0) {
                    // Now filter out unnecessary ones.
                    for (int i = assocVals.size() - 1; i >= 0; i--) {
                        final long[] vals = assocVals.valueAt(i);
                        if (vals[0] < minimum) {
                            assocVals.removeAt(i);
                        }
                    }
                }
            } catch (RemoteException e) {
                // ignore.
                    for (int i = sources.size() - 1; i >= 0; i--) {
                        final SourceState src = sources.valueAt(i);
                        long duration = src.mDuration;
                        if (src.mNesting > 0) {
                            duration += now - src.mStartUptime;
                        }
                        if (duration < minimum) {
                            continue;
                        }
        if (!assocVals.isEmpty()) {
            for (int i = assocVals.size() - 1; i >= 0; i--) {
                final SourceKey key = assocVals.keyAt(i);
                final long[] vals = assocVals.valueAt(i);
                        final SourceKey key = sources.keyAt(i);
                        final long token = proto.start(fieldId);
                        final int idx = uidToPkgMap.indexOfKey(key.mUid);
                        ProcessState.writeCompressedProcessName(proto,
@@ -2428,11 +2390,15 @@ public final class ProcessStats implements Parcelable {
                                key.mProcess, key.mPackage,
                                idx >= 0 && uidToPkgMap.valueAt(idx).size() > 1);
                        proto.write(ProcessStatsAssociationProto.ASSOC_UID, key.mUid);
                proto.write(ProcessStatsAssociationProto.TOTAL_COUNT, (int) vals[1]);
                        proto.write(ProcessStatsAssociationProto.TOTAL_COUNT, src.mCount);
                        proto.write(ProcessStatsAssociationProto.TOTAL_DURATION_SECS,
                        (int) (vals[0] / 1000));
                                (int) (duration / 1000));
                        proto.end(token);
                    }
                } catch (RemoteException e) {
                    // ignore.
                }
            }
        }
    }