| /* |
| * Copyright (C) 2018 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.annotation.Nullable; |
| import android.os.Parcel; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.service.procstats.PackageAssociationProcessStatsProto; |
| import android.service.procstats.PackageAssociationSourceProcessStatsProto; |
| import android.util.ArrayMap; |
| import android.util.Pair; |
| import android.util.Slog; |
| import android.util.TimeUtils; |
| import android.util.proto.ProtoOutputStream; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Objects; |
| |
| public final class AssociationState { |
| private static final String TAG = "ProcessStats"; |
| private static final boolean DEBUG = false; |
| |
| private static final boolean VALIDATE_TIMES = false; |
| |
| private final ProcessStats mProcessStats; |
| private final ProcessStats.PackageState mPackageState; |
| private final String mProcessName; |
| private final String mName; |
| |
| private int mTotalNesting; |
| private long mTotalStartUptime; |
| private int mTotalCount; |
| private long mTotalDuration; |
| private int mTotalActiveNesting; |
| private long mTotalActiveStartUptime; |
| private int mTotalActiveCount; |
| private long mTotalActiveDuration; |
| |
| public final class SourceState { |
| final SourceKey mKey; |
| int mProcStateSeq = -1; |
| int mProcState = ProcessStats.STATE_NOTHING; |
| boolean mInTrackingList; |
| int mNesting; |
| int mCount; |
| long mStartUptime; |
| long mDuration; |
| long mTrackingUptime; |
| int mActiveCount; |
| int mActiveProcState = ProcessStats.STATE_NOTHING; |
| long mActiveStartUptime; |
| long mActiveDuration; |
| DurationsTable mActiveDurations; |
| |
| SourceState(SourceKey key) { |
| mKey = key; |
| } |
| |
| public AssociationState getAssociationState() { |
| return AssociationState.this; |
| } |
| |
| public String getProcessName() { |
| return mKey.mProcess; |
| } |
| |
| public int getUid() { |
| return mKey.mUid; |
| } |
| |
| public void trackProcState(int procState, int seq, long now) { |
| procState = ProcessState.PROCESS_STATE_TO_STATE[procState]; |
| if (seq != mProcStateSeq) { |
| mProcStateSeq = seq; |
| mProcState = procState; |
| } else if (procState < mProcState) { |
| mProcState = procState; |
| } |
| if (procState < ProcessStats.STATE_HOME) { |
| // If the proc state has become better than cached, then we want to |
| // start tracking it to count when it is actually active. If it drops |
| // down to cached, we will clean it up when we later evaluate all currently |
| // tracked associations in ProcessStats.updateTrackingAssociationsLocked(). |
| if (!mInTrackingList) { |
| mInTrackingList = true; |
| mTrackingUptime = now; |
| mProcessStats.mTrackingAssociations.add(this); |
| } |
| } |
| } |
| |
| public void stop() { |
| mNesting--; |
| if (mNesting == 0) { |
| final long now = SystemClock.uptimeMillis(); |
| mDuration += now - mStartUptime; |
| stopTracking(now); |
| } |
| } |
| |
| void startActive(long now) { |
| if (mInTrackingList) { |
| if (mActiveStartUptime == 0) { |
| mActiveStartUptime = now; |
| mActiveCount++; |
| AssociationState.this.mTotalActiveNesting++; |
| if (AssociationState.this.mTotalActiveNesting == 1) { |
| AssociationState.this.mTotalActiveCount++; |
| AssociationState.this.mTotalActiveStartUptime = now; |
| } |
| } |
| if (mActiveProcState != mProcState) { |
| if (mActiveProcState != ProcessStats.STATE_NOTHING) { |
| // Currently active proc state changed, need to store the duration |
| // so far and switch tracking to the new proc state. |
| final long addedDuration = mActiveDuration + now - mActiveStartUptime; |
| mActiveStartUptime = now; |
| if (addedDuration != 0) { |
| if (mActiveDurations == null) { |
| makeDurations(); |
| } |
| mActiveDurations.addDuration(mActiveProcState, addedDuration); |
| mActiveDuration = 0; |
| } |
| } |
| mActiveProcState = mProcState; |
| } |
| } else { |
| Slog.wtf(TAG, "startActive while not tracking: " + this); |
| } |
| } |
| |
| void stopActive(long now) { |
| if (mActiveStartUptime != 0) { |
| if (!mInTrackingList) { |
| Slog.wtf(TAG, "stopActive while not tracking: " + this); |
| } |
| final long addedDuration = now - mActiveStartUptime; |
| 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); |
| } |
| |
| } |
| } |
| } |
| } |
| |
| 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; |
| } |
| 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; |
| } |
| } |
| Slog.wtf(TAG, "Stop tracking didn't find in tracking list: " + this); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(64); |
| sb.append("SourceState{").append(Integer.toHexString(System.identityHashCode(this))) |
| .append(" ").append(mKey.mProcess).append("/").append(mKey.mUid); |
| if (mProcState != ProcessStats.STATE_NOTHING) { |
| sb.append(" ").append(DumpUtils.STATE_NAMES[mProcState]).append(" #") |
| .append(mProcStateSeq); |
| } |
| sb.append("}"); |
| return sb.toString(); |
| } |
| } |
| |
| public final class SourceDumpContainer { |
| public final SourceState mState; |
| public long mTotalTime; |
| public long mActiveTime; |
| |
| public SourceDumpContainer(SourceState state) { |
| mState = state; |
| } |
| } |
| |
| public static final class SourceKey { |
| /** |
| * UID, consider this final. Not final just to avoid a temporary object during lookup. |
| */ |
| int mUid; |
| |
| /** |
| * Process name, consider this final. Not final just to avoid a temporary object during |
| * lookup. |
| */ |
| String mProcess; |
| |
| /** |
| * Optional package name, or null; consider this final. Not final just to avoid a |
| * temporary object during lookup. |
| */ |
| @Nullable String mPackage; |
| |
| SourceKey(int uid, String process, String pkg) { |
| mUid = uid; |
| mProcess = process; |
| mPackage = pkg; |
| } |
| |
| public boolean equals(Object o) { |
| if (!(o instanceof SourceKey)) { |
| return false; |
| } |
| SourceKey s = (SourceKey) o; |
| return s.mUid == mUid && Objects.equals(s.mProcess, mProcess) |
| && Objects.equals(s.mPackage, mPackage); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Integer.hashCode(mUid) ^ (mProcess == null ? 0 : mProcess.hashCode()) |
| ^ (mPackage == null ? 0 : (mPackage.hashCode() * 33)); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(64); |
| sb.append("SourceKey{"); |
| UserHandle.formatUid(sb, mUid); |
| sb.append(' '); |
| sb.append(mProcess); |
| sb.append(' '); |
| sb.append(mPackage); |
| sb.append('}'); |
| return sb.toString(); |
| } |
| } |
| |
| /** |
| * All known sources for this target component... uid -> process name -> source state. |
| */ |
| private final ArrayMap<SourceKey, SourceState> mSources = new ArrayMap<>(); |
| |
| private static final SourceKey sTmpSourceKey = new SourceKey(0, null, null); |
| |
| private ProcessState mProc; |
| |
| public AssociationState(ProcessStats processStats, ProcessStats.PackageState packageState, |
| String name, String processName, ProcessState proc) { |
| mProcessStats = processStats; |
| mPackageState = packageState; |
| mName = name; |
| mProcessName = processName; |
| mProc = proc; |
| } |
| |
| public int getUid() { |
| return mPackageState.mUid; |
| } |
| |
| public String getPackage() { |
| return mPackageState.mPackageName; |
| } |
| |
| public String getProcessName() { |
| return mProcessName; |
| } |
| |
| public String getName() { |
| return mName; |
| } |
| |
| public ProcessState getProcess() { |
| return mProc; |
| } |
| |
| public void setProcess(ProcessState proc) { |
| mProc = proc; |
| } |
| |
| public long getTotalDuration(long now) { |
| return mTotalDuration |
| + (mTotalNesting > 0 ? (now - mTotalStartUptime) : 0); |
| } |
| |
| public long getActiveDuration(long now) { |
| return mTotalActiveDuration |
| + (mTotalActiveNesting > 0 ? (now - mTotalActiveStartUptime) : 0); |
| } |
| |
| public SourceState startSource(int uid, String processName, String packageName) { |
| SourceState src; |
| synchronized (sTmpSourceKey) { |
| sTmpSourceKey.mUid = uid; |
| sTmpSourceKey.mProcess = processName; |
| sTmpSourceKey.mPackage = packageName; |
| src = mSources.get(sTmpSourceKey); |
| } |
| if (src == null) { |
| SourceKey key = new SourceKey(uid, processName, packageName); |
| src = new SourceState(key); |
| mSources.put(key, src); |
| } |
| src.mNesting++; |
| if (src.mNesting == 1) { |
| final long now = SystemClock.uptimeMillis(); |
| src.mCount++; |
| src.mStartUptime = now; |
| mTotalNesting++; |
| if (mTotalNesting == 1) { |
| mTotalCount++; |
| mTotalStartUptime = now; |
| } |
| } |
| return src; |
| } |
| |
| public void add(AssociationState other) { |
| mTotalCount += other.mTotalCount; |
| final long origDuration = mTotalDuration; |
| mTotalDuration += other.mTotalDuration; |
| mTotalActiveCount += other.mTotalActiveCount; |
| mTotalActiveDuration += other.mTotalActiveDuration; |
| for (int isrc = other.mSources.size() - 1; isrc >= 0; isrc--) { |
| final SourceKey key = other.mSources.keyAt(isrc); |
| final SourceState otherSrc = other.mSources.valueAt(isrc); |
| SourceState mySrc = mSources.get(key); |
| boolean newSrc = false; |
| if (mySrc == null) { |
| mySrc = new SourceState(key); |
| mSources.put(key, mySrc); |
| newSrc = true; |
| } |
| if (VALIDATE_TIMES) { |
| Slog.w(TAG, "Adding tot duration " + mySrc.mDuration + "+" |
| + otherSrc.mDuration |
| + (newSrc ? " (new)" : " (old)") + " (total " |
| + origDuration + "+" + other.mTotalDuration + ") in source " |
| + mySrc.mKey.mProcess + " to assoc " + mName); |
| if ((mySrc.mDuration + otherSrc.mDuration) > mTotalDuration) { |
| RuntimeException ex = new RuntimeException(); |
| ex.fillInStackTrace(); |
| Slog.w(TAG, "Source tot duration " + mySrc.mDuration + "+" |
| + otherSrc.mDuration |
| + (newSrc ? " (new)" : " (old)") + " exceeds total " |
| + origDuration + "+" + other.mTotalDuration + " in source " |
| + mySrc.mKey.mProcess + " to assoc " + mName, ex); |
| } |
| if (mySrc.mActiveDurations == null && otherSrc.mActiveDurations == null) { |
| Slog.w(TAG, "Adding act duration " + mySrc.mActiveDuration |
| + "+" + otherSrc.mActiveDuration |
| + (newSrc ? " (new)" : " (old)") + " (total " |
| + origDuration + "+" + other.mTotalDuration + ") in source " |
| + mySrc.mKey.mProcess + " to assoc " + mName); |
| if ((mySrc.mActiveDuration + otherSrc.mActiveDuration) > mTotalDuration) { |
| RuntimeException ex = new RuntimeException(); |
| ex.fillInStackTrace(); |
| Slog.w(TAG, "Source act duration " + mySrc.mActiveDuration + "+" |
| + otherSrc.mActiveDuration |
| + (newSrc ? " (new)" : " (old)") + " exceeds total " |
| + origDuration + "+" + other.mTotalDuration + " in source " |
| + mySrc.mKey.mProcess + " to assoc " + mName, ex); |
| } |
| } |
| } |
| 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; |
| } |
| } |
| } |
| } |
| |
| public boolean isInUse() { |
| return mTotalNesting > 0; |
| } |
| |
| public void resetSafely(long now) { |
| if (!isInUse()) { |
| mSources.clear(); |
| mTotalCount = mTotalActiveCount = 0; |
| } else { |
| // 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; |
| } else { |
| mSources.removeAt(isrc); |
| } |
| } |
| mTotalCount = 1; |
| mTotalStartUptime = now; |
| if (mTotalActiveNesting > 0) { |
| mTotalActiveCount = 1; |
| mTotalActiveStartUptime = now; |
| } else { |
| mTotalActiveCount = 0; |
| } |
| } |
| mTotalDuration = mTotalActiveDuration = 0; |
| } |
| |
| public void writeToParcel(ProcessStats stats, Parcel out, long nowUptime) { |
| out.writeInt(mTotalCount); |
| out.writeLong(mTotalDuration); |
| out.writeInt(mTotalActiveCount); |
| out.writeLong(mTotalActiveDuration); |
| final int NSRC = mSources.size(); |
| out.writeInt(NSRC); |
| 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); |
| } |
| } |
| } |
| |
| /** |
| * Returns non-null if all else fine, else a String that describes the error that |
| * caused it to fail. |
| */ |
| public String readFromParcel(ProcessStats stats, Parcel in, int parcelVersion) { |
| mTotalCount = in.readInt(); |
| mTotalDuration = in.readLong(); |
| mTotalActiveCount = in.readInt(); |
| mTotalActiveDuration = in.readLong(); |
| final int NSRC = in.readInt(); |
| if (NSRC < 0 || NSRC > 100000) { |
| 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(); |
| } |
| if (VALIDATE_TIMES) { |
| if (src.mDuration > mTotalDuration) { |
| RuntimeException ex = new RuntimeException(); |
| ex.fillInStackTrace(); |
| Slog.w(TAG, "Reading tot duration " + src.mDuration |
| + " exceeds total " + mTotalDuration + " in source " |
| + src.mKey.mProcess + " to assoc " + mName, ex); |
| } |
| if (src.mActiveDurations == null && src.mActiveDuration > mTotalDuration) { |
| RuntimeException ex = new RuntimeException(); |
| ex.fillInStackTrace(); |
| Slog.w(TAG, "Reading act duration " + src.mActiveDuration |
| + " exceeds total " + mTotalDuration + " in source " |
| + src.mKey.mProcess + " to assoc " + mName, ex); |
| } |
| } |
| mSources.put(key, src); |
| } |
| return null; |
| } |
| |
| public void commitStateTime(long nowUptime) { |
| 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; |
| } |
| } |
| } |
| if (mTotalNesting > 0) { |
| mTotalDuration += nowUptime - mTotalStartUptime; |
| mTotalStartUptime = nowUptime; |
| } |
| if (mTotalActiveNesting > 0) { |
| mTotalActiveDuration += nowUptime - mTotalActiveStartUptime; |
| mTotalActiveStartUptime = nowUptime; |
| } |
| } |
| } |
| |
| public boolean hasProcessOrPackage(String procName) { |
| if (mProcessName.equals(procName)) { |
| return true; |
| } |
| final int NSRC = mSources.size(); |
| for (int isrc = 0; isrc < NSRC; isrc++) { |
| final SourceKey key = mSources.keyAt(isrc); |
| if (procName.equals(key.mProcess) || procName.equals(key.mPackage)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static final Comparator<Pair<SourceKey, SourceDumpContainer>> ASSOCIATION_COMPARATOR = |
| (o1, o2) -> { |
| if (o1.second.mActiveTime != o2.second.mActiveTime) { |
| return o1.second.mActiveTime > o2.second.mActiveTime ? -1 : 1; |
| } |
| if (o1.second.mTotalTime != o2.second.mTotalTime) { |
| return o1.second.mTotalTime > o2.second.mTotalTime ? -1 : 1; |
| } |
| if (o1.first.mUid != o2.first.mUid) { |
| return o1.first.mUid < o2.first.mUid ? -1 : 1; |
| } |
| if (o1.first.mProcess != o2.first.mProcess) { |
| int diff = o1.first.mProcess.compareTo(o2.first.mProcess); |
| if (diff != 0) { |
| return diff; |
| } |
| } |
| 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); |
| final SourceDumpContainer cont = new SourceDumpContainer(src); |
| long duration = src.mDuration; |
| if (src.mNesting > 0) { |
| duration += now - src.mStartUptime; |
| } |
| cont.mTotalTime = duration; |
| cont.mActiveTime = dumpTime(null, null, src, totalTime, now, false, false); |
| if (cont.mActiveTime < 0) { |
| cont.mActiveTime = -cont.mActiveTime; |
| } |
| sources.add(new Pair<>(mSources.keyAt(isrc), cont)); |
| } |
| Collections.sort(sources, ASSOCIATION_COMPARATOR); |
| return sources; |
| } |
| |
| public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix, |
| ArrayList<Pair<SourceKey, SourceDumpContainer>> sources, long now, long totalTime, |
| String reqPackage, boolean dumpDetails, boolean dumpAll) { |
| final String prefixInnerInner = prefixInner + " "; |
| long totalDuration = mTotalActiveDuration; |
| if (mTotalActiveNesting > 0) { |
| totalDuration += now - mTotalActiveStartUptime; |
| } |
| if (totalDuration > 0 || mTotalActiveCount != 0) { |
| pw.print(prefix); |
| pw.print("Active count "); |
| pw.print(mTotalActiveCount); |
| if (dumpAll) { |
| pw.print(": "); |
| TimeUtils.formatDuration(totalDuration, pw); |
| pw.print(" / "); |
| } else { |
| pw.print(": time "); |
| } |
| DumpUtils.printPercent(pw, (double) totalDuration / (double) totalTime); |
| pw.println(); |
| } |
| if (dumpAll && mTotalActiveNesting != 0) { |
| pw.print(prefix); |
| pw.print("mTotalActiveNesting="); |
| pw.print(mTotalActiveNesting); |
| pw.print(" mTotalActiveStartUptime="); |
| TimeUtils.formatDuration(mTotalActiveStartUptime, now, pw); |
| pw.println(); |
| } |
| totalDuration = mTotalDuration; |
| if (mTotalNesting > 0) { |
| totalDuration += now - mTotalStartUptime; |
| } |
| if (totalDuration > 0 || mTotalCount != 0) { |
| pw.print(prefix); |
| pw.print("Total count "); |
| pw.print(mTotalCount); |
| if (dumpAll) { |
| pw.print(": "); |
| TimeUtils.formatDuration(totalDuration, pw); |
| pw.print(" / "); |
| } else { |
| pw.print(": time "); |
| } |
| DumpUtils.printPercent(pw, (double) totalDuration / (double) totalTime); |
| pw.println(); |
| } |
| if (dumpAll && mTotalNesting != 0) { |
| pw.print(prefix); |
| pw.print("mTotalNesting="); |
| pw.print(mTotalNesting); |
| pw.print(" mTotalStartUptime="); |
| TimeUtils.formatDuration(mTotalStartUptime, now, pw); |
| pw.println(); |
| } |
| final int NSRC = sources.size(); |
| for (int isrc = 0; isrc < NSRC; isrc++) { |
| final SourceKey key = sources.get(isrc).first; |
| final SourceDumpContainer cont = sources.get(isrc).second; |
| final SourceState src = cont.mState; |
| pw.print(prefix); |
| pw.print("<- "); |
| pw.print(key.mProcess); |
| pw.print("/"); |
| UserHandle.formatUid(pw, key.mUid); |
| if (key.mPackage != null) { |
| pw.print(" ("); |
| pw.print(key.mPackage); |
| pw.print(")"); |
| } |
| // If we are skipping this one, we still print the first line just to give |
| // context for the others (so it is clear the total times for the overall |
| // association come from other sources whose times are not shown). |
| if (reqPackage != null && !reqPackage.equals(key.mProcess) |
| && !reqPackage.equals(key.mPackage)) { |
| pw.println(); |
| continue; |
| } |
| pw.println(":"); |
| if (src.mActiveCount != 0 || src.mActiveDurations != null || src.mActiveDuration != 0 |
| || src.mActiveStartUptime != 0) { |
| pw.print(prefixInner); |
| pw.print(" Active count "); |
| pw.print(src.mActiveCount); |
| if (dumpDetails) { |
| if (dumpAll) { |
| if (src.mActiveDurations != null) { |
| pw.print(" (multi-state)"); |
| } else if (src.mActiveProcState >= ProcessStats.STATE_PERSISTENT) { |
| pw.print(" ("); |
| pw.print(DumpUtils.STATE_NAMES[src.mActiveProcState]); |
| pw.print(")"); |
| } else { |
| pw.print(" (*UNKNOWN STATE*)"); |
| } |
| } |
| if (dumpAll) { |
| pw.print(": "); |
| TimeUtils.formatDuration(cont.mActiveTime, pw); |
| pw.print(" / "); |
| } else { |
| pw.print(": time "); |
| } |
| DumpUtils.printPercent(pw, (double) cont.mActiveTime / (double) totalTime); |
| if (src.mActiveStartUptime != 0) { |
| pw.print(" (running)"); |
| } |
| pw.println(); |
| if (src.mActiveDurations != null) { |
| dumpTime(pw, prefixInnerInner, src, totalTime, now, dumpDetails, dumpAll); |
| } |
| } else { |
| pw.print(": "); |
| dumpActiveDurationSummary(pw, src, totalTime, now, dumpAll); |
| } |
| } |
| pw.print(prefixInner); |
| pw.print(" Total count "); |
| pw.print(src.mCount); |
| if (dumpAll) { |
| pw.print(": "); |
| TimeUtils.formatDuration(cont.mTotalTime, pw); |
| pw.print(" / "); |
| } else { |
| pw.print(": time "); |
| } |
| DumpUtils.printPercent(pw, (double) cont.mTotalTime / (double) totalTime); |
| if (src.mNesting > 0) { |
| pw.print(" (running"); |
| if (dumpAll) { |
| pw.print(" nest="); |
| pw.print(src.mNesting); |
| } |
| if (src.mProcState != ProcessStats.STATE_NOTHING) { |
| pw.print(" / "); |
| pw.print(DumpUtils.STATE_NAMES[src.mProcState]); |
| pw.print(" #"); |
| pw.print(src.mProcStateSeq); |
| } |
| pw.print(")"); |
| } |
| pw.println(); |
| if (dumpAll) { |
| if (src.mInTrackingList) { |
| pw.print(prefixInner); |
| pw.print(" mInTrackingList="); |
| pw.println(src.mInTrackingList); |
| } |
| if (src.mProcState != ProcessStats.STATE_NOTHING) { |
| pw.print(prefixInner); |
| pw.print(" mProcState="); |
| pw.print(DumpUtils.STATE_NAMES[src.mProcState]); |
| pw.print(" mProcStateSeq="); |
| pw.println(src.mProcStateSeq); |
| } |
| } |
| } |
| } |
| |
| 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; |
| if (isRunning) { |
| duration = -duration; |
| } |
| if (dumpAll) { |
| TimeUtils.formatDuration(duration, pw); |
| pw.print(" / "); |
| } else { |
| pw.print("time "); |
| } |
| DumpUtils.printPercent(pw, (double) duration / (double) totalTime); |
| if (src.mActiveStartUptime > 0) { |
| pw.print(" (running)"); |
| } |
| pw.println(); |
| } |
| |
| 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++) { |
| long time; |
| if (src.mActiveDurations != null) { |
| time = src.mActiveDurations.getValueForId((byte) iprocstate); |
| } else { |
| time = src.mActiveProcState == iprocstate ? src.mActiveDuration : 0; |
| } |
| final String running; |
| if (src.mActiveStartUptime != 0 && src.mActiveProcState == iprocstate) { |
| running = " (running)"; |
| isRunning = true; |
| time += now - src.mActiveStartUptime; |
| } else { |
| running = null; |
| } |
| if (time != 0) { |
| if (pw != null) { |
| pw.print(prefix); |
| pw.print(DumpUtils.STATE_LABELS[iprocstate]); |
| pw.print(": "); |
| if (dumpAll) { |
| TimeUtils.formatDuration(time, pw); |
| pw.print(" / "); |
| } else { |
| pw.print("time "); |
| } |
| DumpUtils.printPercent(pw, (double) time / (double) overallTime); |
| if (running != null) { |
| pw.print(running); |
| } |
| pw.println(); |
| } |
| totalTime += time; |
| } |
| } |
| return isRunning ? -totalTime : totalTime; |
| } |
| |
| public void dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers, |
| String associationName, long now) { |
| final int NSRC = mSources.size(); |
| for (int isrc = 0; isrc < NSRC; isrc++) { |
| final SourceKey key = mSources.keyAt(isrc); |
| final SourceState src = mSources.valueAt(isrc); |
| pw.print("pkgasc"); |
| pw.print(","); |
| pw.print(pkgName); |
| pw.print(","); |
| pw.print(uid); |
| pw.print(","); |
| pw.print(vers); |
| pw.print(","); |
| pw.print(associationName); |
| pw.print(","); |
| pw.print(key.mProcess); |
| pw.print(","); |
| pw.print(key.mUid); |
| pw.print(","); |
| pw.print(src.mCount); |
| long duration = src.mDuration; |
| if (src.mNesting > 0) { |
| duration += now - src.mStartUptime; |
| } |
| pw.print(","); |
| pw.print(duration); |
| pw.print(","); |
| pw.print(src.mActiveCount); |
| final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0; |
| if (src.mActiveDurations != null) { |
| final int N = src.mActiveDurations.getKeyCount(); |
| for (int i=0; i<N; i++) { |
| final int dkey = src.mActiveDurations.getKeyAt(i); |
| duration = src.mActiveDurations.getValue(dkey); |
| if (dkey == src.mActiveProcState) { |
| duration += timeNow; |
| } |
| final int procState = SparseMappingTable.getIdFromKey(dkey); |
| pw.print(","); |
| DumpUtils.printArrayEntry(pw, DumpUtils.STATE_TAGS, procState, 1); |
| pw.print(':'); |
| pw.print(duration); |
| } |
| } else { |
| duration = src.mActiveDuration + timeNow; |
| if (duration != 0) { |
| pw.print(","); |
| DumpUtils.printArrayEntry(pw, DumpUtils.STATE_TAGS, src.mActiveProcState, 1); |
| pw.print(':'); |
| pw.print(duration); |
| } |
| } |
| pw.println(); |
| } |
| } |
| |
| public void dumpDebug(ProtoOutputStream proto, long fieldId, long now) { |
| final long token = proto.start(fieldId); |
| |
| proto.write(PackageAssociationProcessStatsProto.COMPONENT_NAME, mName); |
| |
| proto.write(PackageAssociationProcessStatsProto.TOTAL_COUNT, mTotalCount); |
| proto.write(PackageAssociationProcessStatsProto.TOTAL_DURATION_MS, getTotalDuration(now)); |
| if (mTotalActiveCount != 0) { |
| proto.write(PackageAssociationProcessStatsProto.ACTIVE_COUNT, mTotalActiveCount); |
| proto.write(PackageAssociationProcessStatsProto.ACTIVE_DURATION_MS, |
| getActiveDuration(now)); |
| } |
| |
| final int NSRC = mSources.size(); |
| for (int isrc = 0; isrc < NSRC; isrc++) { |
| final SourceKey key = mSources.keyAt(isrc); |
| final SourceState src = mSources.valueAt(isrc); |
| final long sourceToken = proto.start(PackageAssociationProcessStatsProto.SOURCES); |
| proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_NAME, key.mProcess); |
| proto.write(PackageAssociationSourceProcessStatsProto.PACKAGE_NAME, key.mPackage); |
| proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_UID, key.mUid); |
| proto.write(PackageAssociationSourceProcessStatsProto.TOTAL_COUNT, src.mCount); |
| long duration = src.mDuration; |
| if (src.mNesting > 0) { |
| duration += now - src.mStartUptime; |
| } |
| proto.write(PackageAssociationSourceProcessStatsProto.TOTAL_DURATION_MS, duration); |
| if (src.mActiveCount != 0) { |
| proto.write(PackageAssociationSourceProcessStatsProto.ACTIVE_COUNT, |
| src.mActiveCount); |
| } |
| final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0; |
| if (src.mActiveDurations != null) { |
| final int N = src.mActiveDurations.getKeyCount(); |
| for (int i=0; i<N; i++) { |
| final int dkey = src.mActiveDurations.getKeyAt(i); |
| duration = src.mActiveDurations.getValue(dkey); |
| if (dkey == src.mActiveProcState) { |
| duration += timeNow; |
| } |
| final int procState = SparseMappingTable.getIdFromKey(dkey); |
| final long stateToken = proto.start( |
| PackageAssociationSourceProcessStatsProto.ACTIVE_STATE_STATS); |
| DumpUtils.printProto(proto, |
| PackageAssociationSourceProcessStatsProto.StateStats.PROCESS_STATE, |
| DumpUtils.STATE_PROTO_ENUMS, procState, 1); |
| proto.write(PackageAssociationSourceProcessStatsProto.StateStats.DURATION_MS, |
| duration); |
| proto.end(stateToken); |
| } |
| } else { |
| duration = src.mActiveDuration + timeNow; |
| if (duration != 0) { |
| final long stateToken = proto.start( |
| PackageAssociationSourceProcessStatsProto.ACTIVE_STATE_STATS); |
| DumpUtils.printProto(proto, |
| PackageAssociationSourceProcessStatsProto.StateStats.PROCESS_STATE, |
| DumpUtils.STATE_PROTO_ENUMS, src.mActiveProcState, 1); |
| proto.write(PackageAssociationSourceProcessStatsProto.StateStats.DURATION_MS, |
| duration); |
| proto.end(stateToken); |
| } |
| } |
| proto.end(sourceToken); |
| } |
| |
| proto.end(token); |
| } |
| |
| public String toString() { |
| return "AssociationState{" + Integer.toHexString(System.identityHashCode(this)) |
| + " " + mName + " pkg=" + mPackageState.mPackageName + " proc=" |
| + Integer.toHexString(System.identityHashCode(mProc)) + "}"; |
| } |
| } |