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