diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index d259b7fe07821..87e8ac1be611a 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -473,6 +473,7 @@ public final class ProcessState { } } mCurCombinedState = state; + mStats.mUidStates.get(mUid).updateCombinedState(state, now); } } diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index fd16662f1b68a..b7abe73a79ebf 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -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> mPackages = new ProcessMap<>(); public final ProcessMap mProcesses = new ProcessMap<>(); + public final SparseArray mUidStates = new SparseArray<>(); + public final ArrayList mTrackingAssociations = new ArrayList<>(); public final long[] mMemFactorDurations = new long[ADJ_COUNT]; @@ -337,6 +341,18 @@ public final class ProcessStats implements Parcelable { } } + SparseArray 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> procMap = other.mProcesses.getMap(); for (int ip=0; ip uids = procMap.valueAt(ip); @@ -358,9 +374,21 @@ 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= 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 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 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) { diff --git a/core/java/com/android/internal/app/procstats/ProcessStatsInternal.java b/core/java/com/android/internal/app/procstats/ProcessStatsInternal.java new file mode 100644 index 0000000000000..2f2300893e323 --- /dev/null +++ b/core/java/com/android/internal/app/procstats/ProcessStatsInternal.java @@ -0,0 +1,31 @@ +/* + * 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 getUidProcStateStatsOverTime(long minTime); +} diff --git a/core/java/com/android/internal/app/procstats/UidState.java b/core/java/com/android/internal/app/procstats/UidState.java new file mode 100644 index 0000000000000..8761b7470cd33 --- /dev/null +++ b/core/java/com/android/internal/app/procstats/UidState.java @@ -0,0 +1,315 @@ +/* + * 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 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(); + } + } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5330252a6d9b6..980772ac50e68 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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, diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java index 34a0c1beec09b..256cffde34558 100644 --- a/services/core/java/com/android/server/am/ProcessStatsService.java +++ b/services/core/java/com/android/server/am/ProcessStatsService.java @@ -38,8 +38,11 @@ import com.android.internal.app.procstats.DumpUtils; import com.android.internal.app.procstats.IProcessStats; import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; +import com.android.internal.app.procstats.ProcessStatsInternal; import com.android.internal.app.procstats.ServiceState; +import com.android.internal.app.procstats.UidState; import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; import java.io.File; import java.io.FileDescriptor; @@ -783,6 +786,64 @@ public final class ProcessStatsService extends IProcessStats.Stub { } } + private SparseArray getUidProcStateStatsOverTime(long minTime) { + final Parcel current = Parcel.obtain(); + final ProcessStats stats = new ProcessStats(); + long curTime; + synchronized (mLock) { + final long now = SystemClock.uptimeMillis(); + mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); + mProcessStats.mTimePeriodEndUptime = now; + stats.add(mProcessStats); + curTime = mProcessStats.mTimePeriodEndRealtime - mProcessStats.mTimePeriodStartRealtime; + } + if (curTime < minTime) { + try { + mFileLock.lock(); + // Need to add in older stats to reach desired time. + ArrayList files = getCommittedFilesLF(0, false, true); + if (files != null && files.size() > 0) { + int i = files.size() - 1; + while (i >= 0 && (stats.mTimePeriodEndRealtime + - stats.mTimePeriodStartRealtime) < minTime) { + AtomicFile file = new AtomicFile(new File(files.get(i))); + i--; + ProcessStats moreStats = new ProcessStats(false); + readLF(moreStats, file); + if (moreStats.mReadError == null) { + stats.add(moreStats); + } else { + Slog.w(TAG, "Failure reading " + files.get(i + 1) + "; " + + moreStats.mReadError); + continue; + } + } + } + } finally { + mFileLock.unlock(); + } + } + final SparseArray uidStates = stats.mUidStates; + final SparseArray results = new SparseArray<>(); + for (int i = 0, size = uidStates.size(); i < size; i++) { + final int uid = uidStates.keyAt(i); + final UidState uidState = uidStates.valueAt(i); + results.put(uid, uidState.getAggregatedDurationsInStates()); + } + return results; + } + + void publish() { + LocalServices.addService(ProcessStatsInternal.class, new LocalService()); + } + + private final class LocalService extends ProcessStatsInternal { + @Override + public SparseArray getUidProcStateStatsOverTime(long minTime) { + return ProcessStatsService.this.getUidProcStateStatsOverTime(minTime); + } + } + private void dumpAggregatedStats(PrintWriter pw, long aggregateHours, long now, String reqPackage, boolean isCompact, boolean dumpDetails, boolean dumpFullDetails, boolean dumpAll, boolean activeOnly, int section) {