1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.app.procstats; 18 19 import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT; 20 import static com.android.internal.app.procstats.ProcessStats.STATE_NOTHING; 21 22 import android.os.Parcel; 23 import android.os.UserHandle; 24 import android.text.TextUtils; 25 import android.util.ArraySet; 26 import android.util.TimeUtils; 27 28 import java.io.PrintWriter; 29 30 /** 31 * The class to track the individual time-in-state of a UID. 32 */ 33 public final class UidState { 34 private static final String TAG = "ProcessStats"; 35 36 private final ProcessStats mStats; 37 private final int mUid; 38 private final DurationsTable mDurations; 39 40 private ArraySet<ProcessState> mProcesses = new ArraySet<>(); 41 private int mCurCombinedState = STATE_NOTHING; 42 private long mStartTime; 43 44 private long mTotalRunningStartTime; 45 private long mTotalRunningDuration; 46 47 /** 48 * Create a new UID state. The initial state is not running. 49 */ UidState(ProcessStats processStats, int uid)50 public UidState(ProcessStats processStats, int uid) { 51 mStats = processStats; 52 mUid = uid; 53 mDurations = new DurationsTable(processStats.mTableData); 54 } 55 56 /** 57 * Create a copy of this instance. 58 */ clone()59 public UidState clone() { 60 UidState unew = new UidState(mStats, mUid); 61 unew.mDurations.addDurations(mDurations); 62 unew.mCurCombinedState = mCurCombinedState; 63 unew.mStartTime = mStartTime; 64 unew.mTotalRunningStartTime = mTotalRunningStartTime; 65 unew.mTotalRunningDuration = mTotalRunningDuration; 66 return unew; 67 } 68 69 /** 70 * Update the current state of the UID, it should be a combination 71 * of all running processes in this UID. 72 */ updateCombinedState(int state, long now)73 public void updateCombinedState(int state, long now) { 74 if (mCurCombinedState != state) { 75 updateCombinedState(now); 76 } 77 } 78 79 /** 80 * Update the current state of the UID, it should be a combination 81 * of all running processes in this UID. 82 */ updateCombinedState(long now)83 public void updateCombinedState(long now) { 84 setCombinedStateInner(calcCombinedState(), now); 85 } 86 calcCombinedState()87 private int calcCombinedState() { 88 int minCombined = STATE_NOTHING; 89 int min = STATE_NOTHING; 90 for (int i = 0, size = mProcesses.size(); i < size; i++) { 91 final int combinedState = mProcesses.valueAt(i).getCombinedState(); 92 final int state = combinedState % STATE_COUNT; 93 if (combinedState != STATE_NOTHING) { 94 if (min == STATE_NOTHING || state < min) { 95 minCombined = combinedState; 96 min = state; 97 } 98 } 99 } 100 return minCombined; 101 } 102 103 /** 104 * Set the combined state and commit the state. 105 * 106 * @param now When it's negative, the previous state won't be committed. 107 */ setCombinedStateInner(int state, long now)108 private void setCombinedStateInner(int state, long now) { 109 if (mCurCombinedState != state) { 110 if (now >= 0) { 111 commitStateTime(now); 112 if (state == STATE_NOTHING) { 113 // We are transitioning to a no longer running state... stop counting run time. 114 mTotalRunningDuration += now - mTotalRunningStartTime; 115 } else if (mCurCombinedState == STATE_NOTHING) { 116 // We previously weren't running... now starting again, clear out total 117 // running info. 118 mTotalRunningDuration = 0; 119 } 120 } 121 mCurCombinedState = state; 122 } 123 } 124 125 /** 126 * @return The current combine state of the UID. 127 */ getCombinedState()128 public int getCombinedState() { 129 return mCurCombinedState; 130 } 131 132 /** 133 * Commit the current state's duration into stats. 134 */ commitStateTime(long now)135 public void commitStateTime(long now) { 136 if (mCurCombinedState != STATE_NOTHING) { 137 long dur = now - mStartTime; 138 if (dur > 0) { 139 mDurations.addDuration(mCurCombinedState, dur); 140 } 141 mTotalRunningDuration += now - mTotalRunningStartTime; 142 mTotalRunningStartTime = now; 143 } 144 mStartTime = now; 145 } 146 147 /** 148 * Reset the UID stats safely. 149 */ resetSafely(long now)150 public void resetSafely(long now) { 151 mDurations.resetTable(); 152 mStartTime = now; 153 mProcesses.removeIf(p -> !p.isInUse()); 154 } 155 156 /** 157 * @return Whether this UID stats is still being used or not. 158 */ isInUse()159 public boolean isInUse() { 160 for (int i = 0, size = mProcesses.size(); i < size; i++) { 161 if (mProcesses.valueAt(i).isInUse()) { 162 return true; 163 } 164 } 165 return false; 166 } 167 168 /** 169 * @return Whether the given package belongs to this UID or not. 170 */ hasPackage(String packageName)171 public boolean hasPackage(String packageName) { 172 for (int i = 0, size = mProcesses.size(); i < size; i++) { 173 final ProcessState proc = mProcesses.valueAt(i); 174 if (TextUtils.equals(packageName, proc.getName()) 175 && TextUtils.equals(packageName, proc.getPackage())) { 176 return true; 177 } 178 } 179 return false; 180 } 181 182 /** 183 * Add stats data from another instance to this one. 184 */ add(UidState other)185 public void add(UidState other) { 186 mDurations.addDurations(other.mDurations); 187 mTotalRunningDuration += other.mTotalRunningDuration; 188 } 189 addProcess(ProcessState proc)190 void addProcess(ProcessState proc) { 191 mProcesses.add(proc); 192 } 193 addProcess(ProcessState proc, long now)194 void addProcess(ProcessState proc, long now) { 195 mProcesses.add(proc); 196 setCombinedStateInner(proc.getCombinedState(), now); 197 } 198 removeProcess(ProcessState proc, long now)199 void removeProcess(ProcessState proc, long now) { 200 mProcesses.remove(proc); 201 setCombinedStateInner(proc.getCombinedState(), now); 202 } 203 204 /** 205 * @return The total amount of stats it's currently tracking. 206 */ getDurationsBucketCount()207 public int getDurationsBucketCount() { 208 return mDurations.getKeyCount(); 209 } 210 211 /** 212 * @return The total running duration of this UID. 213 */ getTotalRunningDuration(long now)214 public long getTotalRunningDuration(long now) { 215 return mTotalRunningDuration 216 + (mTotalRunningStartTime != 0 ? (now - mTotalRunningStartTime) : 0); 217 } 218 219 /** 220 * @return The duration in the given state. 221 */ getDuration(int state, long now)222 public long getDuration(int state, long now) { 223 long time = mDurations.getValueForId((byte) state); 224 if (mCurCombinedState == state) { 225 time += now - mStartTime; 226 } 227 return time; 228 } 229 230 /** 231 * @return The durations in each process state, the mem/screen factors 232 * are consolidated into the bucket with the same process state. 233 */ getAggregatedDurationsInStates()234 public long[] getAggregatedDurationsInStates() { 235 final long[] states = new long[STATE_COUNT]; 236 final int numOfBuckets = getDurationsBucketCount(); 237 for (int i = 0; i < numOfBuckets; i++) { 238 final int key = mDurations.getKeyAt(i); 239 final int combinedState = SparseMappingTable.getIdFromKey(key); 240 states[combinedState % STATE_COUNT] += mDurations.getValue(key); 241 } 242 return states; 243 } 244 writeToParcel(Parcel out, long now)245 void writeToParcel(Parcel out, long now) { 246 mDurations.writeToParcel(out); 247 out.writeLong(getTotalRunningDuration(now)); 248 } 249 readFromParcel(Parcel in)250 boolean readFromParcel(Parcel in) { 251 if (!mDurations.readFromParcel(in)) { 252 return false; 253 } 254 mTotalRunningDuration = in.readLong(); 255 return true; 256 } 257 258 @Override toString()259 public String toString() { 260 StringBuilder sb = new StringBuilder(128); 261 sb.append("UidState{").append(Integer.toHexString(System.identityHashCode(this))) 262 .append(" ").append(UserHandle.formatUid(mUid)).append("}"); 263 return sb.toString(); 264 } 265 dumpState(PrintWriter pw, String prefix, int[] screenStates, int[] memStates, int[] procStates, long now)266 void dumpState(PrintWriter pw, String prefix, 267 int[] screenStates, int[] memStates, int[] procStates, long now) { 268 long totalTime = 0; 269 int printedScreen = -1; 270 for (int is = 0; is < screenStates.length; is++) { 271 int printedMem = -1; 272 for (int im = 0; im < memStates.length; im++) { 273 for (int ip = 0; ip < procStates.length; ip++) { 274 final int iscreen = screenStates[is]; 275 final int imem = memStates[im]; 276 final int bucket = ((iscreen + imem) * STATE_COUNT) + procStates[ip]; 277 long time = mDurations.getValueForId((byte) bucket); 278 String running = ""; 279 if (mCurCombinedState == bucket) { 280 running = " (running)"; 281 time += now - mStartTime; 282 } 283 if (time != 0) { 284 pw.print(prefix); 285 if (screenStates.length > 1) { 286 DumpUtils.printScreenLabel(pw, printedScreen != iscreen 287 ? iscreen : STATE_NOTHING); 288 printedScreen = iscreen; 289 } 290 if (memStates.length > 1) { 291 DumpUtils.printMemLabel(pw, 292 printedMem != imem ? imem : STATE_NOTHING, '/'); 293 printedMem = imem; 294 } 295 pw.print(DumpUtils.STATE_LABELS[procStates[ip]]); pw.print(": "); 296 TimeUtils.formatDuration(time, pw); pw.println(running); 297 totalTime += time; 298 } 299 } 300 } 301 } 302 if (totalTime != 0) { 303 pw.print(prefix); 304 if (screenStates.length > 1) { 305 DumpUtils.printScreenLabel(pw, STATE_NOTHING); 306 } 307 if (memStates.length > 1) { 308 DumpUtils.printMemLabel(pw, STATE_NOTHING, '/'); 309 } 310 pw.print(DumpUtils.STATE_LABEL_TOTAL); 311 pw.print(": "); 312 TimeUtils.formatDuration(totalTime, pw); 313 pw.println(); 314 } 315 } 316 } 317