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
This commit is contained in:
Jing Ji
2021-04-26 17:40:54 -07:00
parent d7d47480fd
commit 276ff21ada
3 changed files with 471 additions and 251 deletions

View File

@@ -16,8 +16,10 @@
package com.android.internal.app.procstats;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.os.UserHandle;
import android.service.procstats.PackageAssociationProcessStatsProto;
@@ -54,7 +56,14 @@ public final class AssociationState {
private int mTotalActiveCount;
private long mTotalActiveDuration;
public final class SourceState {
/**
* The state of the source process of an association.
*/
public static final class SourceState implements Parcelable {
private @NonNull final ProcessStats mProcessStats;
private @Nullable final AssociationState mAssociationState;
private @Nullable final ProcessState mTargetProcess;
private @Nullable SourceState mCommonSourceState;
final SourceKey mKey;
int mProcStateSeq = -1;
int mProcState = ProcessStats.STATE_NOTHING;
@@ -64,18 +73,24 @@ public final class AssociationState {
long mStartUptime;
long mDuration;
long mTrackingUptime;
int mActiveNesting;
int mActiveCount;
int mActiveProcState = ProcessStats.STATE_NOTHING;
long mActiveStartUptime;
long mActiveDuration;
DurationsTable mActiveDurations;
SourceState(SourceKey key) {
SourceState(@NonNull ProcessStats processStats, @Nullable AssociationState associationState,
@NonNull ProcessState targetProcess, SourceKey key) {
mProcessStats = processStats;
mAssociationState = associationState;
mTargetProcess = targetProcess;
mKey = key;
}
@Nullable
public AssociationState getAssociationState() {
return AssociationState.this;
return mAssociationState;
}
public String getProcessName() {
@@ -86,7 +101,20 @@ public final class AssociationState {
return mKey.mUid;
}
@Nullable
private SourceState getCommonSourceState(boolean createIfNeeded) {
if (mCommonSourceState == null) {
if (createIfNeeded) {
mCommonSourceState = mTargetProcess.getOrCreateSourceState(mKey);
} else {
Slog.wtf(TAG, "Unable to find common source state for " + mKey.mProcess);
}
}
return mCommonSourceState;
}
public void trackProcState(int procState, int seq, long now) {
final int processState = procState;
procState = ProcessState.PROCESS_STATE_TO_STATE[procState];
if (seq != mProcStateSeq) {
mProcStateSeq = seq;
@@ -102,30 +130,81 @@ public final class AssociationState {
if (!mInTrackingList) {
mInTrackingList = true;
mTrackingUptime = now;
mProcessStats.mTrackingAssociations.add(this);
if (mAssociationState != null) {
mProcessStats.mTrackingAssociations.add(this);
}
}
}
if (mAssociationState != null) {
final SourceState commonSource = getCommonSourceState(true);
if (commonSource != null) {
commonSource.trackProcState(processState, seq, now);
}
}
}
long start() {
final long now = start(-1);
if (mAssociationState != null) {
final SourceState commonSource = getCommonSourceState(true);
if (commonSource != null) {
commonSource.start(now);
}
}
return now;
}
long start(long now) {
mNesting++;
if (mNesting == 1) {
if (now < 0) {
now = SystemClock.uptimeMillis();
}
mCount++;
mStartUptime = now;
}
return now;
}
public void stop() {
mNesting--;
if (mNesting == 0) {
final long now = SystemClock.uptimeMillis();
mDuration += now - mStartUptime;
stopTracking(now);
final long now = stop(-1);
if (mAssociationState != null) {
final SourceState commonSource = getCommonSourceState(false);
if (commonSource != null) {
commonSource.stop(now);
}
}
}
long stop(long now) {
mNesting--;
if (mNesting == 0) {
if (now < 0) {
now = SystemClock.uptimeMillis();
}
mDuration += now - mStartUptime;
stopTracking(now);
}
return now;
}
void startActive(long now) {
boolean startActive = false;
if (mInTrackingList) {
if (mActiveStartUptime == 0) {
mActiveStartUptime = now;
mActiveNesting++;
mActiveCount++;
AssociationState.this.mTotalActiveNesting++;
if (AssociationState.this.mTotalActiveNesting == 1) {
AssociationState.this.mTotalActiveCount++;
AssociationState.this.mTotalActiveStartUptime = now;
startActive = true;
if (mAssociationState != null) {
mAssociationState.mTotalActiveNesting++;
if (mAssociationState.mTotalActiveNesting == 1) {
mAssociationState.mTotalActiveCount++;
mAssociationState.mTotalActiveStartUptime = now;
}
}
} else if (mAssociationState == null) {
mActiveNesting++;
}
if (mActiveProcState != mProcState) {
if (mActiveProcState != ProcessStats.STATE_NOTHING) {
@@ -133,6 +212,9 @@ public final class AssociationState {
// so far and switch tracking to the new proc state.
final long addedDuration = mActiveDuration + now - mActiveStartUptime;
mActiveStartUptime = now;
if (mAssociationState != null) {
startActive = true;
}
if (addedDuration != 0) {
if (mActiveDurations == null) {
makeDurations();
@@ -146,68 +228,233 @@ public final class AssociationState {
} else {
Slog.wtf(TAG, "startActive while not tracking: " + this);
}
if (mAssociationState != null) {
final SourceState commonSource = getCommonSourceState(true);
if (commonSource != null && startActive) {
commonSource.startActive(now);
}
}
}
void stopActive(long now) {
boolean stopActive = false;
if (mActiveStartUptime != 0) {
if (!mInTrackingList) {
if (!mInTrackingList && mAssociationState != null) {
Slog.wtf(TAG, "stopActive while not tracking: " + this);
}
mActiveNesting--;
final long addedDuration = now - mActiveStartUptime;
mActiveStartUptime = 0;
mActiveStartUptime = mAssociationState != null || mActiveNesting == 0 ? 0 : now;
stopActive = mActiveStartUptime == 0;
if (mActiveDurations != null) {
mActiveDurations.addDuration(mActiveProcState, addedDuration);
} else {
mActiveDuration += addedDuration;
}
AssociationState.this.mTotalActiveNesting--;
if (AssociationState.this.mTotalActiveNesting == 0) {
AssociationState.this.mTotalActiveDuration += now
- AssociationState.this.mTotalActiveStartUptime;
AssociationState.this.mTotalActiveStartUptime = 0;
if (VALIDATE_TIMES) {
if (mActiveDuration > AssociationState.this.mTotalActiveDuration) {
RuntimeException ex = new RuntimeException();
ex.fillInStackTrace();
Slog.w(TAG, "Source act duration " + mActiveDurations
+ " exceeds total " + AssociationState.this.mTotalActiveDuration
+ " in procstate " + mActiveProcState + " in source "
+ mKey.mProcess + " to assoc "
+ AssociationState.this.mName, ex);
}
if (mAssociationState != null) {
mAssociationState.mTotalActiveNesting--;
if (mAssociationState.mTotalActiveNesting == 0) {
mAssociationState.mTotalActiveDuration += now
- mAssociationState.mTotalActiveStartUptime;
mAssociationState.mTotalActiveStartUptime = 0;
if (VALIDATE_TIMES) {
if (mActiveDuration > mAssociationState.mTotalActiveDuration) {
RuntimeException ex = new RuntimeException();
ex.fillInStackTrace();
Slog.w(TAG, "Source act duration " + mActiveDurations
+ " exceeds total " + mAssociationState.mTotalActiveDuration
+ " in procstate " + mActiveProcState + " in source "
+ mKey.mProcess + " to assoc "
+ mAssociationState.mName, ex);
}
}
}
}
}
if (mAssociationState != null) {
final SourceState commonSource = getCommonSourceState(false);
if (commonSource != null && stopActive) {
commonSource.stopActive(now);
}
}
}
boolean stopActiveIfNecessary(int curSeq, long now) {
if (mProcStateSeq != curSeq || 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.
stopActive(now);
stopTrackingProcState();
return true;
}
return false;
}
private void stopTrackingProcState() {
mInTrackingList = false;
mProcState = ProcessStats.STATE_NOTHING;
if (mAssociationState != null) {
final SourceState commonSource = getCommonSourceState(false);
if (commonSource != null) {
commonSource.stopTrackingProcState();
}
}
}
boolean isInUse() {
return mNesting > 0;
}
void resetSafely(long now) {
if (isInUse()) {
mCount = 1;
mStartUptime = now;
mDuration = 0;
if (mActiveStartUptime > 0) {
mActiveCount = 1;
mActiveStartUptime = now;
} else {
mActiveCount = 0;
}
mActiveDuration = 0;
mActiveDurations = null;
}
}
void commitStateTime(long nowUptime) {
if (mNesting > 0) {
mDuration += nowUptime - mStartUptime;
mStartUptime = nowUptime;
}
if (mActiveStartUptime > 0) {
final long addedDuration = nowUptime - mActiveStartUptime;
mActiveStartUptime = nowUptime;
if (mActiveDurations != null) {
mActiveDurations.addDuration(mActiveProcState, addedDuration);
} else {
mActiveDuration += addedDuration;
}
}
}
void makeDurations() {
mActiveDurations = new DurationsTable(mProcessStats.mTableData);
}
void stopTracking(long now) {
AssociationState.this.mTotalNesting--;
if (AssociationState.this.mTotalNesting == 0) {
AssociationState.this.mTotalDuration += now
- AssociationState.this.mTotalStartUptime;
private void stopTracking(long now) {
if (mAssociationState != null) {
mAssociationState.mTotalNesting--;
if (mAssociationState.mTotalNesting == 0) {
mAssociationState.mTotalDuration += now
- mAssociationState.mTotalStartUptime;
}
}
stopActive(now);
if (mInTrackingList) {
mInTrackingList = false;
mProcState = ProcessStats.STATE_NOTHING;
// Do a manual search for where to remove, since these objects will typically
// be towards the end of the array.
final ArrayList<SourceState> list = mProcessStats.mTrackingAssociations;
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i) == this) {
list.remove(i);
return;
if (mAssociationState != null) {
// Do a manual search for where to remove, since these objects will typically
// be towards the end of the array.
final ArrayList<SourceState> list = mProcessStats.mTrackingAssociations;
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i) == this) {
list.remove(i);
return;
}
}
Slog.wtf(TAG, "Stop tracking didn't find in tracking list: " + this);
}
Slog.wtf(TAG, "Stop tracking didn't find in tracking list: " + this);
}
}
void add(SourceState otherSrc) {
mCount += otherSrc.mCount;
mDuration += otherSrc.mDuration;
mActiveCount += otherSrc.mActiveCount;
if (otherSrc.mActiveDuration != 0 || otherSrc.mActiveDurations != null) {
// Only need to do anything if the other one has some duration data.
if (mActiveDurations != null) {
// If the target already has multiple durations, just add in whatever
// we have in the other.
if (otherSrc.mActiveDurations != null) {
mActiveDurations.addDurations(otherSrc.mActiveDurations);
} else {
mActiveDurations.addDuration(otherSrc.mActiveProcState,
otherSrc.mActiveDuration);
}
} else if (otherSrc.mActiveDurations != null) {
// The other one has multiple durations, but we don't. Expand to
// multiple durations and copy over.
makeDurations();
mActiveDurations.addDurations(otherSrc.mActiveDurations);
if (mActiveDuration != 0) {
mActiveDurations.addDuration(mActiveProcState, mActiveDuration);
mActiveDuration = 0;
mActiveProcState = ProcessStats.STATE_NOTHING;
}
} else if (mActiveDuration != 0) {
// Both have a single inline duration... we can either add them together,
// or need to expand to multiple durations.
if (mActiveProcState == otherSrc.mActiveProcState) {
mActiveDuration += otherSrc.mActiveDuration;
} else {
// The two have durations with different proc states, need to turn
// in to multiple durations.
makeDurations();
mActiveDurations.addDuration(mActiveProcState, mActiveDuration);
mActiveDurations.addDuration(otherSrc.mActiveProcState,
otherSrc.mActiveDuration);
mActiveDuration = 0;
mActiveProcState = ProcessStats.STATE_NOTHING;
}
} else {
// The other one has a duration, and we know the target doesn't. Copy over.
mActiveProcState = otherSrc.mActiveProcState;
mActiveDuration = otherSrc.mActiveDuration;
}
}
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mCount);
out.writeLong(mDuration);
out.writeInt(mActiveCount);
if (mActiveDurations != null) {
out.writeInt(1);
mActiveDurations.writeToParcel(out);
} else {
out.writeInt(0);
out.writeInt(mActiveProcState);
out.writeLong(mActiveDuration);
}
}
@Override
public int describeContents() {
return 0;
}
String readFromParcel(Parcel in) {
mCount = in.readInt();
mDuration = in.readLong();
mActiveCount = in.readInt();
if (in.readInt() != 0) {
makeDurations();
if (!mActiveDurations.readFromParcel(in)) {
return "Duration table corrupt: " + mKey + " <- " + toString();
}
} else {
mActiveProcState = in.readInt();
mActiveDuration = in.readLong();
}
return null;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(64);
@@ -222,7 +469,7 @@ public final class AssociationState {
}
}
public final class SourceDumpContainer {
static final class SourceDumpContainer {
public final SourceState mState;
public long mTotalTime;
public long mActiveTime;
@@ -256,6 +503,18 @@ public final class AssociationState {
mPackage = pkg;
}
SourceKey(ProcessStats stats, Parcel in, int parcelVersion) {
mUid = in.readInt();
mProcess = stats.readCommonString(in, parcelVersion);
mPackage = stats.readCommonString(in, parcelVersion);
}
void writeToParcel(ProcessStats stats, Parcel out) {
out.writeInt(mUid);
stats.writeCommonString(out, mProcess);
stats.writeCommonString(out, mPackage);
}
public boolean equals(Object o) {
if (!(o instanceof SourceKey)) {
return false;
@@ -347,14 +606,11 @@ public final class AssociationState {
}
if (src == null) {
SourceKey key = new SourceKey(uid, processName, packageName);
src = new SourceState(key);
src = new SourceState(mProcessStats, this, mProc, key);
mSources.put(key, src);
}
src.mNesting++;
if (src.mNesting == 1) {
final long now = SystemClock.uptimeMillis();
src.mCount++;
src.mStartUptime = now;
final long now = src.start();
if (now > 0) {
mTotalNesting++;
if (mTotalNesting == 1) {
mTotalCount++;
@@ -376,7 +632,7 @@ public final class AssociationState {
SourceState mySrc = mSources.get(key);
boolean newSrc = false;
if (mySrc == null) {
mySrc = new SourceState(key);
mySrc = new SourceState(mProcessStats, this, mProc, key);
mSources.put(key, mySrc);
newSrc = true;
}
@@ -412,53 +668,7 @@ public final class AssociationState {
}
}
}
mySrc.mCount += otherSrc.mCount;
mySrc.mDuration += otherSrc.mDuration;
mySrc.mActiveCount += otherSrc.mActiveCount;
if (otherSrc.mActiveDuration != 0 || otherSrc.mActiveDurations != null) {
// Only need to do anything if the other one has some duration data.
if (mySrc.mActiveDurations != null) {
// If the target already has multiple durations, just add in whatever
// we have in the other.
if (otherSrc.mActiveDurations != null) {
mySrc.mActiveDurations.addDurations(otherSrc.mActiveDurations);
} else {
mySrc.mActiveDurations.addDuration(otherSrc.mActiveProcState,
otherSrc.mActiveDuration);
}
} else if (otherSrc.mActiveDurations != null) {
// The other one has multiple durations, but we don't. Expand to
// multiple durations and copy over.
mySrc.makeDurations();
mySrc.mActiveDurations.addDurations(otherSrc.mActiveDurations);
if (mySrc.mActiveDuration != 0) {
mySrc.mActiveDurations.addDuration(mySrc.mActiveProcState,
mySrc.mActiveDuration);
mySrc.mActiveDuration = 0;
mySrc.mActiveProcState = ProcessStats.STATE_NOTHING;
}
} else if (mySrc.mActiveDuration != 0) {
// Both have a single inline duration... we can either add them together,
// or need to expand to multiple durations.
if (mySrc.mActiveProcState == otherSrc.mActiveProcState) {
mySrc.mActiveDuration += otherSrc.mActiveDuration;
} else {
// The two have durations with different proc states, need to turn
// in to multiple durations.
mySrc.makeDurations();
mySrc.mActiveDurations.addDuration(mySrc.mActiveProcState,
mySrc.mActiveDuration);
mySrc.mActiveDurations.addDuration(otherSrc.mActiveProcState,
otherSrc.mActiveDuration);
mySrc.mActiveDuration = 0;
mySrc.mActiveProcState = ProcessStats.STATE_NOTHING;
}
} else {
// The other one has a duration, and we know the target doesn't. Copy over.
mySrc.mActiveProcState = otherSrc.mActiveProcState;
mySrc.mActiveDuration = otherSrc.mActiveDuration;
}
}
mySrc.add(otherSrc);
}
}
@@ -474,18 +684,8 @@ public final class AssociationState {
// We have some active sources... clear out everything but those.
for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) {
SourceState src = mSources.valueAt(isrc);
if (src.mNesting > 0) {
src.mCount = 1;
src.mStartUptime = now;
src.mDuration = 0;
if (src.mActiveStartUptime > 0) {
src.mActiveCount = 1;
src.mActiveStartUptime = now;
} else {
src.mActiveCount = 0;
}
src.mActiveDuration = 0;
src.mActiveDurations = null;
if (src.isInUse()) {
src.resetSafely(now);
} else {
mSources.removeAt(isrc);
}
@@ -512,20 +712,8 @@ public final class AssociationState {
for (int isrc = 0; isrc < NSRC; isrc++) {
final SourceKey key = mSources.keyAt(isrc);
final SourceState src = mSources.valueAt(isrc);
out.writeInt(key.mUid);
stats.writeCommonString(out, key.mProcess);
stats.writeCommonString(out, key.mPackage);
out.writeInt(src.mCount);
out.writeLong(src.mDuration);
out.writeInt(src.mActiveCount);
if (src.mActiveDurations != null) {
out.writeInt(1);
src.mActiveDurations.writeToParcel(out);
} else {
out.writeInt(0);
out.writeInt(src.mActiveProcState);
out.writeLong(src.mActiveDuration);
}
key.writeToParcel(stats, out);
src.writeToParcel(out, 0);
}
}
@@ -543,22 +731,11 @@ public final class AssociationState {
return "Association with bad src count: " + NSRC;
}
for (int isrc = 0; isrc < NSRC; isrc++) {
final int uid = in.readInt();
final String procName = stats.readCommonString(in, parcelVersion);
final String pkgName = stats.readCommonString(in, parcelVersion);
final SourceKey key = new SourceKey(uid, procName, pkgName);
final SourceState src = new SourceState(key);
src.mCount = in.readInt();
src.mDuration = in.readLong();
src.mActiveCount = in.readInt();
if (in.readInt() != 0) {
src.makeDurations();
if (!src.mActiveDurations.readFromParcel(in)) {
return "Duration table corrupt: " + key + " <- " + src;
}
} else {
src.mActiveProcState = in.readInt();
src.mActiveDuration = in.readLong();
final SourceKey key = new SourceKey(stats, in, parcelVersion);
final SourceState src = new SourceState(mProcessStats, this, mProc, key);
final String errMsg = src.readFromParcel(in);
if (errMsg != null) {
return errMsg;
}
if (VALIDATE_TIMES) {
if (src.mDuration > mTotalDuration) {
@@ -585,19 +762,7 @@ public final class AssociationState {
if (isInUse()) {
for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) {
SourceState src = mSources.valueAt(isrc);
if (src.mNesting > 0) {
src.mDuration += nowUptime - src.mStartUptime;
src.mStartUptime = nowUptime;
}
if (src.mActiveStartUptime > 0) {
final long addedDuration = nowUptime - src.mActiveStartUptime;
src.mActiveStartUptime = nowUptime;
if (src.mActiveDurations != null) {
src.mActiveDurations.addDuration(src.mActiveProcState, addedDuration);
} else {
src.mActiveDuration += addedDuration;
}
}
src.commitStateTime(nowUptime);
}
if (mTotalNesting > 0) {
mTotalDuration += nowUptime - mTotalStartUptime;
@@ -644,12 +809,12 @@ public final class AssociationState {
return 0;
};
public ArrayList<Pair<SourceKey, SourceDumpContainer>> createSortedAssociations(long now,
long totalTime) {
final int NSRC = mSources.size();
ArrayList<Pair<SourceKey, SourceDumpContainer>> sources = new ArrayList<>(NSRC);
for (int isrc = 0; isrc < NSRC; isrc++) {
final SourceState src = mSources.valueAt(isrc);
static ArrayList<Pair<SourceKey, SourceDumpContainer>> createSortedAssociations(long now,
long totalTime, ArrayMap<SourceKey, SourceState> inSources) {
final int numOfSources = inSources.size();
ArrayList<Pair<SourceKey, SourceDumpContainer>> sources = new ArrayList<>(numOfSources);
for (int isrc = 0; isrc < numOfSources; isrc++) {
final SourceState src = inSources.valueAt(isrc);
final SourceDumpContainer cont = new SourceDumpContainer(src);
long duration = src.mDuration;
if (src.mNesting > 0) {
@@ -660,7 +825,7 @@ public final class AssociationState {
if (cont.mActiveTime < 0) {
cont.mActiveTime = -cont.mActiveTime;
}
sources.add(new Pair<>(mSources.keyAt(isrc), cont));
sources.add(new Pair<>(inSources.keyAt(isrc), cont));
}
Collections.sort(sources, ASSOCIATION_COMPARATOR);
return sources;
@@ -722,6 +887,14 @@ public final class AssociationState {
TimeUtils.formatDuration(mTotalStartUptime, now, pw);
pw.println();
}
dumpSources(pw, prefix, prefixInner, prefixInnerInner, sources, now, totalTime,
reqPackage, dumpDetails, dumpAll);
}
static void dumpSources(PrintWriter pw, String prefix, String prefixInner,
String prefixInnerInner, ArrayList<Pair<SourceKey, SourceDumpContainer>> sources,
long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll) {
final int NSRC = sources.size();
for (int isrc = 0; isrc < NSRC; isrc++) {
final SourceKey key = sources.get(isrc).first;
@@ -826,7 +999,7 @@ public final class AssociationState {
}
}
void dumpActiveDurationSummary(PrintWriter pw, final SourceState src, long totalTime,
static void dumpActiveDurationSummary(PrintWriter pw, final SourceState src, long totalTime,
long now, boolean dumpAll) {
long duration = dumpTime(null, null, src, totalTime, now, false, false);
final boolean isRunning = duration < 0;
@@ -846,8 +1019,8 @@ public final class AssociationState {
pw.println();
}
long dumpTime(PrintWriter pw, String prefix, final SourceState src, long overallTime, long now,
boolean dumpDetails, boolean dumpAll) {
static long dumpTime(PrintWriter pw, String prefix, final SourceState src, long overallTime,
long now, boolean dumpDetails, boolean dumpAll) {
long totalTime = 0;
boolean isRunning = false;
for (int iprocstate = 0; iprocstate < ProcessStats.STATE_COUNT; iprocstate++) {

View File

@@ -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);
}
}

View File

@@ -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,85 +2357,47 @@ 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 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);
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();
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;
}
final SourceKey key = sources.keyAt(i);
final long token = proto.start(fieldId);
final int idx = uidToPkgMap.indexOfKey(key.mUid);
ProcessState.writeCompressedProcessName(proto,
ProcessStatsAssociationProto.ASSOC_PROCESS_NAME,
key.mProcess, key.mPackage,
idx >= 0 && uidToPkgMap.valueAt(idx).size() > 1);
proto.write(ProcessStatsAssociationProto.ASSOC_UID, key.mUid);
proto.write(ProcessStatsAssociationProto.TOTAL_COUNT, src.mCount);
proto.write(ProcessStatsAssociationProto.TOTAL_DURATION_SECS,
(int) (duration / 1000));
proto.end(token);
}
} catch (RemoteException e) {
// ignore.
}
} catch (RemoteException e) {
// ignore.
}
}
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 long token = proto.start(fieldId);
final int idx = uidToPkgMap.indexOfKey(key.mUid);
ProcessState.writeCompressedProcessName(proto,
ProcessStatsAssociationProto.ASSOC_PROCESS_NAME,
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_DURATION_SECS,
(int) (vals[0] / 1000));
proto.end(token);
}
}
}