Support time-in-state proc stats for UIDs

Bug: 200326767
Test: atest ProcStatsValidationTests
Test: atest ProcessStatsDumpsysTest
Test: Manual - dumpsys procstats -a
Change-Id: Ic2da178c244706bb43488e57fb808a77225bad6d
This commit is contained in:
Jing Ji
2021-10-18 09:56:54 -07:00
parent b3691198da
commit ccfcee5bd1
6 changed files with 521 additions and 5 deletions

View File

@@ -473,6 +473,7 @@ public final class ProcessState {
}
}
mCurCombinedState = state;
mStats.mUidStates.get(mUid).updateCombinedState(state, now);
}
}

View File

@@ -179,17 +179,19 @@ public final class ProcessStats implements Parcelable {
public static final int REPORT_PKG_ASC_STATS = 0x08;
// Should report package stats.
public static final int REPORT_PKG_STATS = 0x0E;
// Should report uid stats.
public static final int REPORT_UID_STATS = 0x10;
// Should report all stats.
public static final int REPORT_ALL = 0x0F;
public static final int REPORT_ALL = 0x1F;
public static final int[] OPTIONS =
{REPORT_PROC_STATS, REPORT_PKG_PROC_STATS, REPORT_PKG_SVC_STATS, REPORT_PKG_ASC_STATS,
REPORT_PKG_STATS, REPORT_ALL};
REPORT_PKG_STATS, REPORT_UID_STATS, REPORT_ALL};
public static final String[] OPTIONS_STR =
{"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "all"};
{"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "uid", "all"};
// Current version of the parcel format.
private static final int PARCEL_VERSION = 40;
private static final int PARCEL_VERSION = 41;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0x50535454;
@@ -200,6 +202,8 @@ public final class ProcessStats implements Parcelable {
public final ProcessMap<LongSparseArray<PackageState>> mPackages = new ProcessMap<>();
public final ProcessMap<ProcessState> mProcesses = new ProcessMap<>();
public final SparseArray<UidState> mUidStates = new SparseArray<>();
public final ArrayList<AssociationState.SourceState> mTrackingAssociations = new ArrayList<>();
public final long[] mMemFactorDurations = new long[ADJ_COUNT];
@@ -337,6 +341,18 @@ public final class ProcessStats implements Parcelable {
}
}
SparseArray<UidState> uidStates = other.mUidStates;
for (int ip = 0, size = uidStates.size(); ip < size; ip++) {
final int uid = uidStates.keyAt(ip);
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
uidState = uidStates.valueAt(ip).clone();
mUidStates.put(uid, uidState);
} else {
uidState.add(uidStates.valueAt(ip));
}
}
ArrayMap<String, SparseArray<ProcessState>> procMap = other.mProcesses.getMap();
for (int ip=0; ip<procMap.size(); ip++) {
SparseArray<ProcessState> uids = procMap.valueAt(ip);
@@ -358,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<ADJ_COUNT; i++) {
if (DEBUG) Slog.d(TAG, "Total duration #" + i + " inc by "
+ other.mMemFactorDurations[i] + " from "
@@ -488,6 +516,7 @@ public final class ProcessStats implements Parcelable {
resetCommon();
mPackages.getMap().clear();
mProcesses.getMap().clear();
mUidStates.clear();
mMemFactor = STATE_NOTHING;
mStartTime = 0;
if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr);
@@ -580,6 +609,7 @@ public final class ProcessStats implements Parcelable {
}
} else {
ps.makeDead();
mUidStates.get(uids.keyAt(iu)).removeProcess(ps, now);
uids.removeAt(iu);
}
}
@@ -588,6 +618,15 @@ public final class ProcessStats implements Parcelable {
}
}
for (int ip = mUidStates.size() - 1; ip >= 0; ip--) {
final UidState uidState = mUidStates.valueAt(ip);
if (uidState.isInUse()) {
mUidStates.valueAt(ip).resetSafely(now);
} else {
mUidStates.removeAt(ip);
}
}
mStartTime = now;
if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr);
}
@@ -900,6 +939,13 @@ public final class ProcessStats implements Parcelable {
mSysMemUsage.writeToParcel(out);
final int numOfUids = mUidStates.size();
out.writeInt(numOfUids);
for (int ip = 0; ip < numOfUids; ip++) {
out.writeInt(mUidStates.keyAt(ip));
mUidStates.valueAt(ip).writeToParcel(out, now);
}
out.writeInt(NPROC);
for (int ip=0; ip<NPROC; ip++) {
writeCommonString(out, procMap.keyAt(ip));
@@ -1026,7 +1072,8 @@ public final class ProcessStats implements Parcelable {
public void readFromParcel(Parcel in) {
final boolean hadData = mPackages.getMap().size() > 0
|| mProcesses.getMap().size() > 0;
|| mProcesses.getMap().size() > 0
|| mUidStates.size() > 0;
if (hadData) {
resetSafely();
}
@@ -1083,6 +1130,16 @@ public final class ProcessStats implements Parcelable {
return;
}
final int numOfUids = in.readInt();
for (int ip = 0; ip < numOfUids; ip++) {
final int uid = in.readInt();
final UidState uidState = new UidState(this, uid);
if (!uidState.readFromParcel(in)) {
return;
}
mUidStates.put(uid, uidState);
}
int NPROC = in.readInt();
if (NPROC < 0) {
mReadError = "bad process count: " + NPROC;
@@ -1127,9 +1184,17 @@ public final class ProcessStats implements Parcelable {
if (DEBUG_PARCEL) Slog.d(TAG, "Adding process: " + procName + " " + uid
+ " " + proc);
mProcesses.put(procName, uid, proc);
if (proc.isActive()) {
mUidStates.get(uid).addProcess(proc);
}
}
}
for (int ip = 0; ip < numOfUids; ip++) {
mUidStates.valueAt(ip).updateCombinedState(-1);
}
if (DEBUG_PARCEL) Slog.d(TAG, "Read " + mProcesses.getMap().size() + " processes");
int NPKG = in.readInt();
@@ -1327,6 +1392,12 @@ public final class ProcessStats implements Parcelable {
commonProc = new ProcessState(this, pkgState.mPackageName, pkgState.mUid,
pkgState.mVersionCode, processName);
mProcesses.put(processName, pkgState.mUid, commonProc);
UidState uidState = mUidStates.get(pkgState.mUid);
if (uidState == null) {
uidState = new UidState(this, pkgState.mUid);
mUidStates.put(pkgState.mUid, uidState);
}
uidState.addProcess(commonProc);
if (DEBUG) Slog.d(TAG, "GETPROC created new common " + commonProc);
}
if (!commonProc.isMultiPackage()) {
@@ -1787,6 +1858,42 @@ public final class ProcessStats implements Parcelable {
pw.print(" shown of "); pw.print(numTotalProcs); pw.println(" total");
}
if ((section & REPORT_UID_STATS) != 0) {
SparseArray<UidState> uidStates = mUidStates;
int numShownUids = 0, numTotalUids = 0;
printedHeader = false;
for (int iu = 0, size = uidStates.size(); iu < size; iu++) {
final int uid = uidStates.keyAt(iu);
final UidState uidState = uidStates.valueAt(iu);
numTotalUids++;
if (reqPackage != null && !uidState.hasPackage(reqPackage)) {
continue;
}
numShownUids++;
pw.println();
if (!printedHeader) {
pw.println("Per-UID Stats:");
printedHeader = true;
}
if (activeOnly && !uidState.isInUse()) {
pw.print(" (Not active: ");
pw.print(UserHandle.formatUid(uid));
pw.println(")");
continue;
}
pw.print(" * ");
UserHandle.formatUid(pw, uid);
pw.print(" (");
pw.print(uidState.getDurationsBucketCount());
pw.print(" entries)");
pw.println(":");
uidState.dumpState(pw, " ", ALL_SCREEN_ADJ, ALL_MEM_ADJ,
ALL_PROC_STATES, now);
}
pw.print(" Total UIDs: "); pw.print(numShownUids);
pw.print(" shown of "); pw.print(numTotalUids); pw.println(" total");
}
if (dumpAll) {
pw.println();
if (mTrackingAssociations.size() > 0) {

View File

@@ -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<long[]> getUidProcStateStatsOverTime(long minTime);
}

View File

@@ -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<ProcessState> mProcesses = new ArraySet<>();
private int mCurCombinedState = STATE_NOTHING;
private long mStartTime;
private long mTotalRunningStartTime;
private long mTotalRunningDuration;
/**
* Create a new UID state. The initial state is not running.
*/
public UidState(ProcessStats processStats, int uid) {
mStats = processStats;
mUid = uid;
mDurations = new DurationsTable(processStats.mTableData);
}
/**
* Create a copy of this instance.
*/
public UidState clone() {
UidState unew = new UidState(mStats, mUid);
unew.mDurations.addDurations(mDurations);
unew.mCurCombinedState = mCurCombinedState;
unew.mStartTime = mStartTime;
unew.mTotalRunningStartTime = mTotalRunningStartTime;
unew.mTotalRunningDuration = mTotalRunningDuration;
return unew;
}
/**
* Update the current state of the UID, it should be a combination
* of all running processes in this UID.
*/
public void updateCombinedState(int state, long now) {
if (mCurCombinedState != state) {
updateCombinedState(now);
}
}
/**
* Update the current state of the UID, it should be a combination
* of all running processes in this UID.
*/
public void updateCombinedState(long now) {
setCombinedStateInner(calcCombinedState(), now);
}
private int calcCombinedState() {
int minCombined = STATE_NOTHING;
int min = STATE_NOTHING;
for (int i = 0, size = mProcesses.size(); i < size; i++) {
final int combinedState = mProcesses.valueAt(i).getCombinedState();
final int state = combinedState % STATE_COUNT;
if (combinedState != STATE_NOTHING) {
if (min == STATE_NOTHING || state < min) {
minCombined = combinedState;
min = state;
}
}
}
return minCombined;
}
/**
* Set the combined state and commit the state.
*
* @param now When it's negative, the previous state won't be committed.
*/
private void setCombinedStateInner(int state, long now) {
if (mCurCombinedState != state) {
if (now >= 0) {
commitStateTime(now);
if (state == STATE_NOTHING) {
// We are transitioning to a no longer running state... stop counting run time.
mTotalRunningDuration += now - mTotalRunningStartTime;
} else if (mCurCombinedState == STATE_NOTHING) {
// We previously weren't running... now starting again, clear out total
// running info.
mTotalRunningDuration = 0;
}
}
mCurCombinedState = state;
}
}
/**
* @return The current combine state of the UID.
*/
public int getCombinedState() {
return mCurCombinedState;
}
/**
* Commit the current state's duration into stats.
*/
public void commitStateTime(long now) {
if (mCurCombinedState != STATE_NOTHING) {
long dur = now - mStartTime;
if (dur > 0) {
mDurations.addDuration(mCurCombinedState, dur);
}
mTotalRunningDuration += now - mTotalRunningStartTime;
mTotalRunningStartTime = now;
}
mStartTime = now;
}
/**
* Reset the UID stats safely.
*/
public void resetSafely(long now) {
mDurations.resetTable();
mStartTime = now;
}
/**
* @return Whether this UID stats is still being used or not.
*/
public boolean isInUse() {
for (int i = 0, size = mProcesses.size(); i < size; i++) {
if (mProcesses.valueAt(i).isInUse()) {
return true;
}
}
return false;
}
/**
* @return Whether the given package belongs to this UID or not.
*/
public boolean hasPackage(String packageName) {
for (int i = 0, size = mProcesses.size(); i < size; i++) {
final ProcessState proc = mProcesses.valueAt(i);
if (TextUtils.equals(packageName, proc.getName())
&& TextUtils.equals(packageName, proc.getPackage())) {
return true;
}
}
return false;
}
/**
* Add stats data from another instance to this one.
*/
public void add(UidState other) {
mDurations.addDurations(other.mDurations);
mTotalRunningDuration += other.mTotalRunningDuration;
}
void addProcess(ProcessState proc) {
mProcesses.add(proc);
}
void addProcess(ProcessState proc, long now) {
mProcesses.add(proc);
setCombinedStateInner(proc.getCombinedState(), now);
}
void removeProcess(ProcessState proc, long now) {
mProcesses.remove(proc);
setCombinedStateInner(proc.getCombinedState(), now);
}
/**
* @return The total amount of stats it's currently tracking.
*/
public int getDurationsBucketCount() {
return mDurations.getKeyCount();
}
/**
* @return The total running duration of this UID.
*/
public long getTotalRunningDuration(long now) {
return mTotalRunningDuration
+ (mTotalRunningStartTime != 0 ? (now - mTotalRunningStartTime) : 0);
}
/**
* @return The duration in the given state.
*/
public long getDuration(int state, long now) {
long time = mDurations.getValueForId((byte) state);
if (mCurCombinedState == state) {
time += now - mStartTime;
}
return time;
}
/**
* @return The durations in each process state, the mem/screen factors
* are consolidated into the bucket with the same process state.
*/
public long[] getAggregatedDurationsInStates() {
final long[] states = new long[STATE_COUNT];
final int numOfBuckets = getDurationsBucketCount();
for (int i = 0; i < numOfBuckets; i++) {
final int key = mDurations.getKeyAt(i);
final int combinedState = SparseMappingTable.getIdFromKey(key);
states[combinedState % STATE_COUNT] += mDurations.getValue(key);
}
return states;
}
void writeToParcel(Parcel out, long now) {
mDurations.writeToParcel(out);
out.writeLong(getTotalRunningDuration(now));
}
boolean readFromParcel(Parcel in) {
if (!mDurations.readFromParcel(in)) {
return false;
}
mTotalRunningDuration = in.readLong();
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("UidState{").append(Integer.toHexString(System.identityHashCode(this)))
.append(" ").append(UserHandle.formatUid(mUid)).append("}");
return sb.toString();
}
void dumpState(PrintWriter pw, String prefix,
int[] screenStates, int[] memStates, int[] procStates, long now) {
long totalTime = 0;
int printedScreen = -1;
for (int is = 0; is < screenStates.length; is++) {
int printedMem = -1;
for (int im = 0; im < memStates.length; im++) {
for (int ip = 0; ip < procStates.length; ip++) {
final int iscreen = screenStates[is];
final int imem = memStates[im];
final int bucket = ((iscreen + imem) * STATE_COUNT) + procStates[ip];
long time = mDurations.getValueForId((byte) bucket);
String running = "";
if (mCurCombinedState == bucket) {
running = " (running)";
time += now - mStartTime;
}
if (time != 0) {
pw.print(prefix);
if (screenStates.length > 1) {
DumpUtils.printScreenLabel(pw, printedScreen != iscreen
? iscreen : STATE_NOTHING);
printedScreen = iscreen;
}
if (memStates.length > 1) {
DumpUtils.printMemLabel(pw,
printedMem != imem ? imem : STATE_NOTHING, '/');
printedMem = imem;
}
pw.print(DumpUtils.STATE_LABELS[procStates[ip]]); pw.print(": ");
TimeUtils.formatDuration(time, pw); pw.println(running);
totalTime += time;
}
}
}
}
if (totalTime != 0) {
pw.print(prefix);
if (screenStates.length > 1) {
DumpUtils.printScreenLabel(pw, STATE_NOTHING);
}
if (memStates.length > 1) {
DumpUtils.printMemLabel(pw, STATE_NOTHING, '/');
}
pw.print(DumpUtils.STATE_LABEL_TOTAL);
pw.print(": ");
TimeUtils.formatDuration(totalTime, pw);
pw.println();
}
}
}

View File

@@ -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,

View File

@@ -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<long[]> 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<String> 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<UidState> uidStates = stats.mUidStates;
final SparseArray<long[]> 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<long[]> 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) {