1 /*
2  * Copyright (C) 2018 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 android.annotation.Nullable;
20 import android.os.Parcel;
21 import android.os.SystemClock;
22 import android.os.UserHandle;
23 import android.service.procstats.PackageAssociationProcessStatsProto;
24 import android.service.procstats.PackageAssociationSourceProcessStatsProto;
25 import android.util.ArrayMap;
26 import android.util.Pair;
27 import android.util.Slog;
28 import android.util.TimeUtils;
29 import android.util.proto.ProtoOutputStream;
30 
31 import java.io.PrintWriter;
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.Comparator;
35 import java.util.Objects;
36 
37 public final class AssociationState {
38     private static final String TAG = "ProcessStats";
39     private static final boolean DEBUG = false;
40 
41     private static final boolean VALIDATE_TIMES = false;
42 
43     private final ProcessStats mProcessStats;
44     private final ProcessStats.PackageState mPackageState;
45     private final String mProcessName;
46     private final String mName;
47 
48     private int mTotalNesting;
49     private long mTotalStartUptime;
50     private int mTotalCount;
51     private long mTotalDuration;
52     private int mTotalActiveNesting;
53     private long mTotalActiveStartUptime;
54     private int mTotalActiveCount;
55     private long mTotalActiveDuration;
56 
57     public final class SourceState {
58         final SourceKey mKey;
59         int mProcStateSeq = -1;
60         int mProcState = ProcessStats.STATE_NOTHING;
61         boolean mInTrackingList;
62         int mNesting;
63         int mCount;
64         long mStartUptime;
65         long mDuration;
66         long mTrackingUptime;
67         int mActiveCount;
68         int mActiveProcState = ProcessStats.STATE_NOTHING;
69         long mActiveStartUptime;
70         long mActiveDuration;
71         DurationsTable mActiveDurations;
72 
SourceState(SourceKey key)73         SourceState(SourceKey key) {
74             mKey = key;
75         }
76 
getAssociationState()77         public AssociationState getAssociationState() {
78             return AssociationState.this;
79         }
80 
getProcessName()81         public String getProcessName() {
82             return mKey.mProcess;
83         }
84 
getUid()85         public int getUid() {
86             return mKey.mUid;
87         }
88 
trackProcState(int procState, int seq, long now)89         public void trackProcState(int procState, int seq, long now) {
90             procState = ProcessState.PROCESS_STATE_TO_STATE[procState];
91             if (seq != mProcStateSeq) {
92                 mProcStateSeq = seq;
93                 mProcState = procState;
94             } else if (procState < mProcState) {
95                 mProcState = procState;
96             }
97             if (procState < ProcessStats.STATE_HOME) {
98                 // If the proc state has become better than cached, then we want to
99                 // start tracking it to count when it is actually active.  If it drops
100                 // down to cached, we will clean it up when we later evaluate all currently
101                 // tracked associations in ProcessStats.updateTrackingAssociationsLocked().
102                 if (!mInTrackingList) {
103                     mInTrackingList = true;
104                     mTrackingUptime = now;
105                     mProcessStats.mTrackingAssociations.add(this);
106                 }
107             }
108         }
109 
stop()110         public void stop() {
111             mNesting--;
112             if (mNesting == 0) {
113                 final long now = SystemClock.uptimeMillis();
114                 mDuration += now - mStartUptime;
115                 stopTracking(now);
116             }
117         }
118 
startActive(long now)119         void startActive(long now) {
120             if (mInTrackingList) {
121                 if (mActiveStartUptime == 0) {
122                     mActiveStartUptime = now;
123                     mActiveCount++;
124                     AssociationState.this.mTotalActiveNesting++;
125                     if (AssociationState.this.mTotalActiveNesting == 1) {
126                         AssociationState.this.mTotalActiveCount++;
127                         AssociationState.this.mTotalActiveStartUptime = now;
128                     }
129                 }
130                 if (mActiveProcState != mProcState) {
131                     if (mActiveProcState != ProcessStats.STATE_NOTHING) {
132                         // Currently active proc state changed, need to store the duration
133                         // so far and switch tracking to the new proc state.
134                         final long addedDuration = mActiveDuration + now - mActiveStartUptime;
135                         mActiveStartUptime = now;
136                         if (addedDuration != 0) {
137                             if (mActiveDurations == null) {
138                                 makeDurations();
139                             }
140                             mActiveDurations.addDuration(mActiveProcState, addedDuration);
141                             mActiveDuration = 0;
142                         }
143                     }
144                     mActiveProcState = mProcState;
145                 }
146             } else {
147                 Slog.wtf(TAG, "startActive while not tracking: " + this);
148             }
149         }
150 
stopActive(long now)151         void stopActive(long now) {
152             if (mActiveStartUptime != 0) {
153                 if (!mInTrackingList) {
154                     Slog.wtf(TAG, "stopActive while not tracking: " + this);
155                 }
156                 final long addedDuration = now - mActiveStartUptime;
157                 mActiveStartUptime = 0;
158                 if (mActiveDurations != null) {
159                     mActiveDurations.addDuration(mActiveProcState, addedDuration);
160                 } else {
161                     mActiveDuration += addedDuration;
162                 }
163                 AssociationState.this.mTotalActiveNesting--;
164                 if (AssociationState.this.mTotalActiveNesting == 0) {
165                     AssociationState.this.mTotalActiveDuration += now
166                             - AssociationState.this.mTotalActiveStartUptime;
167                     AssociationState.this.mTotalActiveStartUptime = 0;
168                     if (VALIDATE_TIMES) {
169                         if (mActiveDuration > AssociationState.this.mTotalActiveDuration) {
170                             RuntimeException ex = new RuntimeException();
171                             ex.fillInStackTrace();
172                             Slog.w(TAG, "Source act duration " + mActiveDurations
173                                     + " exceeds total " + AssociationState.this.mTotalActiveDuration
174                                     + " in procstate " + mActiveProcState + " in source "
175                                     + mKey.mProcess + " to assoc "
176                                     + AssociationState.this.mName, ex);
177                         }
178 
179                     }
180                 }
181             }
182         }
183 
makeDurations()184         void makeDurations() {
185             mActiveDurations = new DurationsTable(mProcessStats.mTableData);
186         }
187 
stopTracking(long now)188         void stopTracking(long now) {
189             AssociationState.this.mTotalNesting--;
190             if (AssociationState.this.mTotalNesting == 0) {
191                 AssociationState.this.mTotalDuration += now
192                         - AssociationState.this.mTotalStartUptime;
193             }
194             stopActive(now);
195             if (mInTrackingList) {
196                 mInTrackingList = false;
197                 mProcState = ProcessStats.STATE_NOTHING;
198                 // Do a manual search for where to remove, since these objects will typically
199                 // be towards the end of the array.
200                 final ArrayList<SourceState> list = mProcessStats.mTrackingAssociations;
201                 for (int i = list.size() - 1; i >= 0; i--) {
202                     if (list.get(i) == this) {
203                         list.remove(i);
204                         return;
205                     }
206                 }
207                 Slog.wtf(TAG, "Stop tracking didn't find in tracking list: " + this);
208             }
209         }
210 
211         @Override
toString()212         public String toString() {
213             StringBuilder sb = new StringBuilder(64);
214             sb.append("SourceState{").append(Integer.toHexString(System.identityHashCode(this)))
215                     .append(" ").append(mKey.mProcess).append("/").append(mKey.mUid);
216             if (mProcState != ProcessStats.STATE_NOTHING) {
217                 sb.append(" ").append(DumpUtils.STATE_NAMES[mProcState]).append(" #")
218                         .append(mProcStateSeq);
219             }
220             sb.append("}");
221             return sb.toString();
222         }
223     }
224 
225     public final class SourceDumpContainer {
226         public final SourceState mState;
227         public long mTotalTime;
228         public long mActiveTime;
229 
SourceDumpContainer(SourceState state)230         public SourceDumpContainer(SourceState state) {
231             mState = state;
232         }
233     }
234 
235     public static final class SourceKey {
236         /**
237          * UID, consider this final.  Not final just to avoid a temporary object during lookup.
238          */
239         int mUid;
240 
241         /**
242          * Process name, consider this final.  Not final just to avoid a temporary object during
243          * lookup.
244          */
245         String mProcess;
246 
247         /**
248          * Optional package name, or null; consider this final.  Not final just to avoid a
249          * temporary object during lookup.
250          */
251         @Nullable String mPackage;
252 
SourceKey(int uid, String process, String pkg)253         SourceKey(int uid, String process, String pkg) {
254             mUid = uid;
255             mProcess = process;
256             mPackage = pkg;
257         }
258 
equals(Object o)259         public boolean equals(Object o) {
260             if (!(o instanceof SourceKey)) {
261                 return false;
262             }
263             SourceKey s = (SourceKey) o;
264             return s.mUid == mUid && Objects.equals(s.mProcess, mProcess)
265                     && Objects.equals(s.mPackage, mPackage);
266         }
267 
268         @Override
hashCode()269         public int hashCode() {
270             return Integer.hashCode(mUid) ^ (mProcess == null ? 0 : mProcess.hashCode())
271                     ^ (mPackage == null ? 0 : (mPackage.hashCode() * 33));
272         }
273 
274         @Override
toString()275         public String toString() {
276             StringBuilder sb = new StringBuilder(64);
277             sb.append("SourceKey{");
278             UserHandle.formatUid(sb, mUid);
279             sb.append(' ');
280             sb.append(mProcess);
281             sb.append(' ');
282             sb.append(mPackage);
283             sb.append('}');
284             return sb.toString();
285         }
286     }
287 
288     /**
289      * All known sources for this target component...  uid -> process name -> source state.
290      */
291     final ArrayMap<SourceKey, SourceState> mSources = new ArrayMap<>();
292 
293     private static final SourceKey sTmpSourceKey = new SourceKey(0, null, null);
294 
295     private ProcessState mProc;
296 
AssociationState(ProcessStats processStats, ProcessStats.PackageState packageState, String name, String processName, ProcessState proc)297     public AssociationState(ProcessStats processStats, ProcessStats.PackageState packageState,
298             String name, String processName, ProcessState proc) {
299         mProcessStats = processStats;
300         mPackageState = packageState;
301         mName = name;
302         mProcessName = processName;
303         mProc = proc;
304     }
305 
getUid()306     public int getUid() {
307         return mPackageState.mUid;
308     }
309 
getPackage()310     public String getPackage() {
311         return mPackageState.mPackageName;
312     }
313 
getProcessName()314     public String getProcessName() {
315         return mProcessName;
316     }
317 
getName()318     public String getName() {
319         return mName;
320     }
321 
getProcess()322     public ProcessState getProcess() {
323         return mProc;
324     }
325 
setProcess(ProcessState proc)326     public void setProcess(ProcessState proc) {
327         mProc = proc;
328     }
329 
getTotalDuration(long now)330     public long getTotalDuration(long now) {
331         return mTotalDuration
332                 + (mTotalNesting > 0 ? (now - mTotalStartUptime) : 0);
333     }
334 
getActiveDuration(long now)335     public long getActiveDuration(long now) {
336         return mTotalActiveDuration
337                 + (mTotalActiveNesting > 0 ? (now - mTotalActiveStartUptime) : 0);
338     }
339 
startSource(int uid, String processName, String packageName)340     public SourceState startSource(int uid, String processName, String packageName) {
341         SourceState src;
342         synchronized (sTmpSourceKey) {
343             sTmpSourceKey.mUid = uid;
344             sTmpSourceKey.mProcess = processName;
345             sTmpSourceKey.mPackage = packageName;
346             src = mSources.get(sTmpSourceKey);
347         }
348         if (src == null) {
349             SourceKey key = new SourceKey(uid, processName, packageName);
350             src = new SourceState(key);
351             mSources.put(key, src);
352         }
353         src.mNesting++;
354         if (src.mNesting == 1) {
355             final long now = SystemClock.uptimeMillis();
356             src.mCount++;
357             src.mStartUptime = now;
358             mTotalNesting++;
359             if (mTotalNesting == 1) {
360                 mTotalCount++;
361                 mTotalStartUptime = now;
362             }
363         }
364         return src;
365     }
366 
add(AssociationState other)367     public void add(AssociationState other) {
368         mTotalCount += other.mTotalCount;
369         final long origDuration = mTotalDuration;
370         mTotalDuration += other.mTotalDuration;
371         mTotalActiveCount += other.mTotalActiveCount;
372         mTotalActiveDuration += other.mTotalActiveDuration;
373         for (int isrc = other.mSources.size() - 1; isrc >= 0; isrc--) {
374             final SourceKey key = other.mSources.keyAt(isrc);
375             final SourceState otherSrc = other.mSources.valueAt(isrc);
376             SourceState mySrc = mSources.get(key);
377             boolean newSrc = false;
378             if (mySrc == null) {
379                 mySrc = new SourceState(key);
380                 mSources.put(key, mySrc);
381                 newSrc = true;
382             }
383             if (VALIDATE_TIMES) {
384                 Slog.w(TAG, "Adding tot duration " + mySrc.mDuration + "+"
385                         + otherSrc.mDuration
386                         + (newSrc ? " (new)" : " (old)") + " (total "
387                         + origDuration + "+" + other.mTotalDuration + ") in source "
388                         + mySrc.mKey.mProcess + " to assoc " + mName);
389                 if ((mySrc.mDuration + otherSrc.mDuration) > mTotalDuration) {
390                     RuntimeException ex = new RuntimeException();
391                     ex.fillInStackTrace();
392                     Slog.w(TAG, "Source tot duration " + mySrc.mDuration + "+"
393                             + otherSrc.mDuration
394                             + (newSrc ? " (new)" : " (old)") + " exceeds total "
395                             + origDuration + "+" + other.mTotalDuration + " in source "
396                             + mySrc.mKey.mProcess + " to assoc " + mName, ex);
397                 }
398                 if (mySrc.mActiveDurations == null && otherSrc.mActiveDurations == null) {
399                     Slog.w(TAG, "Adding act duration " + mySrc.mActiveDuration
400                             + "+" + otherSrc.mActiveDuration
401                             + (newSrc ? " (new)" : " (old)") + " (total "
402                             + origDuration + "+" + other.mTotalDuration + ") in source "
403                             + mySrc.mKey.mProcess + " to assoc " + mName);
404                     if ((mySrc.mActiveDuration + otherSrc.mActiveDuration) > mTotalDuration) {
405                         RuntimeException ex = new RuntimeException();
406                         ex.fillInStackTrace();
407                         Slog.w(TAG, "Source act duration " + mySrc.mActiveDuration + "+"
408                                 + otherSrc.mActiveDuration
409                                 + (newSrc ? " (new)" : " (old)") + " exceeds total "
410                                 + origDuration + "+" + other.mTotalDuration + " in source "
411                                 + mySrc.mKey.mProcess + " to assoc " + mName, ex);
412                     }
413                 }
414             }
415             mySrc.mCount += otherSrc.mCount;
416             mySrc.mDuration += otherSrc.mDuration;
417             mySrc.mActiveCount += otherSrc.mActiveCount;
418             if (otherSrc.mActiveDuration != 0 || otherSrc.mActiveDurations != null) {
419                 // Only need to do anything if the other one has some duration data.
420                 if (mySrc.mActiveDurations != null) {
421                     // If the target already has multiple durations, just add in whatever
422                     // we have in the other.
423                     if (otherSrc.mActiveDurations != null) {
424                         mySrc.mActiveDurations.addDurations(otherSrc.mActiveDurations);
425                     } else {
426                         mySrc.mActiveDurations.addDuration(otherSrc.mActiveProcState,
427                                 otherSrc.mActiveDuration);
428                     }
429                 } else if (otherSrc.mActiveDurations != null) {
430                     // The other one has multiple durations, but we don't.  Expand to
431                     // multiple durations and copy over.
432                     mySrc.makeDurations();
433                     mySrc.mActiveDurations.addDurations(otherSrc.mActiveDurations);
434                     if (mySrc.mActiveDuration != 0) {
435                         mySrc.mActiveDurations.addDuration(mySrc.mActiveProcState,
436                                 mySrc.mActiveDuration);
437                         mySrc.mActiveDuration = 0;
438                         mySrc.mActiveProcState = ProcessStats.STATE_NOTHING;
439                     }
440                 } else if (mySrc.mActiveDuration != 0) {
441                     // Both have a single inline duration...  we can either add them together,
442                     // or need to expand to multiple durations.
443                     if (mySrc.mActiveProcState == otherSrc.mActiveProcState) {
444                         mySrc.mActiveDuration += otherSrc.mActiveDuration;
445                     } else {
446                         // The two have durations with different proc states, need to turn
447                         // in to multiple durations.
448                         mySrc.makeDurations();
449                         mySrc.mActiveDurations.addDuration(mySrc.mActiveProcState,
450                                 mySrc.mActiveDuration);
451                         mySrc.mActiveDurations.addDuration(otherSrc.mActiveProcState,
452                                 otherSrc.mActiveDuration);
453                         mySrc.mActiveDuration = 0;
454                         mySrc.mActiveProcState = ProcessStats.STATE_NOTHING;
455                     }
456                 } else {
457                     // The other one has a duration, and we know the target doesn't.  Copy over.
458                     mySrc.mActiveProcState = otherSrc.mActiveProcState;
459                     mySrc.mActiveDuration = otherSrc.mActiveDuration;
460                 }
461             }
462         }
463     }
464 
isInUse()465     public boolean isInUse() {
466         return mTotalNesting > 0;
467     }
468 
resetSafely(long now)469     public void resetSafely(long now) {
470         if (!isInUse()) {
471             mSources.clear();
472             mTotalCount = mTotalActiveCount = 0;
473         } else {
474             // We have some active sources...  clear out everything but those.
475             for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) {
476                 SourceState src = mSources.valueAt(isrc);
477                 if (src.mNesting > 0) {
478                     src.mCount = 1;
479                     src.mStartUptime = now;
480                     src.mDuration = 0;
481                     if (src.mActiveStartUptime > 0) {
482                         src.mActiveCount = 1;
483                         src.mActiveStartUptime = now;
484                     } else {
485                         src.mActiveCount = 0;
486                     }
487                     src.mActiveDuration = 0;
488                     src.mActiveDurations = null;
489                 } else {
490                     mSources.removeAt(isrc);
491                 }
492             }
493             mTotalCount = 1;
494             mTotalStartUptime = now;
495             if (mTotalActiveNesting > 0) {
496                 mTotalActiveCount = 1;
497                 mTotalActiveStartUptime = now;
498             } else {
499                 mTotalActiveCount = 0;
500             }
501         }
502         mTotalDuration = mTotalActiveDuration = 0;
503     }
504 
writeToParcel(ProcessStats stats, Parcel out, long nowUptime)505     public void writeToParcel(ProcessStats stats, Parcel out, long nowUptime) {
506         out.writeInt(mTotalCount);
507         out.writeLong(mTotalDuration);
508         out.writeInt(mTotalActiveCount);
509         out.writeLong(mTotalActiveDuration);
510         final int NSRC = mSources.size();
511         out.writeInt(NSRC);
512         for (int isrc = 0; isrc < NSRC; isrc++) {
513             final SourceKey key = mSources.keyAt(isrc);
514             final SourceState src = mSources.valueAt(isrc);
515             out.writeInt(key.mUid);
516             stats.writeCommonString(out, key.mProcess);
517             stats.writeCommonString(out, key.mPackage);
518             out.writeInt(src.mCount);
519             out.writeLong(src.mDuration);
520             out.writeInt(src.mActiveCount);
521             if (src.mActiveDurations != null) {
522                 out.writeInt(1);
523                 src.mActiveDurations.writeToParcel(out);
524             } else {
525                 out.writeInt(0);
526                 out.writeInt(src.mActiveProcState);
527                 out.writeLong(src.mActiveDuration);
528             }
529         }
530     }
531 
532     /**
533      * Returns non-null if all else fine, else a String that describes the error that
534      * caused it to fail.
535      */
readFromParcel(ProcessStats stats, Parcel in, int parcelVersion)536     public String readFromParcel(ProcessStats stats, Parcel in, int parcelVersion) {
537         mTotalCount = in.readInt();
538         mTotalDuration = in.readLong();
539         mTotalActiveCount = in.readInt();
540         mTotalActiveDuration = in.readLong();
541         final int NSRC = in.readInt();
542         if (NSRC < 0 || NSRC > 100000) {
543             return "Association with bad src count: " + NSRC;
544         }
545         for (int isrc = 0; isrc < NSRC; isrc++) {
546             final int uid = in.readInt();
547             final String procName = stats.readCommonString(in, parcelVersion);
548             final String pkgName = stats.readCommonString(in, parcelVersion);
549             final SourceKey key = new SourceKey(uid, procName, pkgName);
550             final SourceState src = new SourceState(key);
551             src.mCount = in.readInt();
552             src.mDuration = in.readLong();
553             src.mActiveCount = in.readInt();
554             if (in.readInt() != 0) {
555                 src.makeDurations();
556                 if (!src.mActiveDurations.readFromParcel(in)) {
557                     return "Duration table corrupt: " + key + " <- " + src;
558                 }
559             } else {
560                 src.mActiveProcState = in.readInt();
561                 src.mActiveDuration = in.readLong();
562             }
563             if (VALIDATE_TIMES) {
564                 if (src.mDuration > mTotalDuration) {
565                     RuntimeException ex = new RuntimeException();
566                     ex.fillInStackTrace();
567                     Slog.w(TAG, "Reading tot duration " + src.mDuration
568                             + " exceeds total " + mTotalDuration + " in source "
569                             + src.mKey.mProcess + " to assoc " + mName, ex);
570                 }
571                 if (src.mActiveDurations == null && src.mActiveDuration > mTotalDuration) {
572                     RuntimeException ex = new RuntimeException();
573                     ex.fillInStackTrace();
574                     Slog.w(TAG, "Reading act duration " + src.mActiveDuration
575                             + " exceeds total " + mTotalDuration + " in source "
576                             + src.mKey.mProcess + " to assoc " + mName, ex);
577                 }
578             }
579             mSources.put(key, src);
580         }
581         return null;
582     }
583 
commitStateTime(long nowUptime)584     public void commitStateTime(long nowUptime) {
585         if (isInUse()) {
586             for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) {
587                 SourceState src = mSources.valueAt(isrc);
588                 if (src.mNesting > 0) {
589                     src.mDuration += nowUptime - src.mStartUptime;
590                     src.mStartUptime = nowUptime;
591                 }
592                 if (src.mActiveStartUptime > 0) {
593                     final long addedDuration = nowUptime - src.mActiveStartUptime;
594                     src.mActiveStartUptime = nowUptime;
595                     if (src.mActiveDurations != null) {
596                         src.mActiveDurations.addDuration(src.mActiveProcState, addedDuration);
597                     } else {
598                         src.mActiveDuration += addedDuration;
599                     }
600                 }
601             }
602             if (mTotalNesting > 0) {
603                 mTotalDuration += nowUptime - mTotalStartUptime;
604                 mTotalStartUptime = nowUptime;
605             }
606             if (mTotalActiveNesting > 0) {
607                 mTotalActiveDuration += nowUptime - mTotalActiveStartUptime;
608                 mTotalActiveStartUptime = nowUptime;
609             }
610         }
611     }
612 
hasProcessOrPackage(String procName)613     public boolean hasProcessOrPackage(String procName) {
614         if (mProcessName.equals(procName)) {
615             return true;
616         }
617         final int NSRC = mSources.size();
618         for (int isrc = 0; isrc < NSRC; isrc++) {
619             final SourceKey key = mSources.keyAt(isrc);
620             if (procName.equals(key.mProcess) || procName.equals(key.mPackage)) {
621                 return true;
622             }
623         }
624         return false;
625     }
626 
627     static final Comparator<Pair<SourceKey, SourceDumpContainer>> ASSOCIATION_COMPARATOR =
628             (o1, o2) -> {
629         if (o1.second.mActiveTime != o2.second.mActiveTime) {
630             return o1.second.mActiveTime > o2.second.mActiveTime ? -1 : 1;
631         }
632         if (o1.second.mTotalTime != o2.second.mTotalTime) {
633             return o1.second.mTotalTime > o2.second.mTotalTime ? -1 : 1;
634         }
635         if (o1.first.mUid != o2.first.mUid) {
636             return o1.first.mUid < o2.first.mUid ? -1 : 1;
637         }
638         if (o1.first.mProcess != o2.first.mProcess) {
639             int diff = o1.first.mProcess.compareTo(o2.first.mProcess);
640             if (diff != 0) {
641                 return diff;
642             }
643         }
644         return 0;
645     };
646 
createSortedAssociations(long now, long totalTime)647     public ArrayList<Pair<SourceKey, SourceDumpContainer>> createSortedAssociations(long now,
648             long totalTime) {
649         final int NSRC = mSources.size();
650         ArrayList<Pair<SourceKey, SourceDumpContainer>> sources = new ArrayList<>(NSRC);
651         for (int isrc = 0; isrc < NSRC; isrc++) {
652             final SourceState src = mSources.valueAt(isrc);
653             final SourceDumpContainer cont = new SourceDumpContainer(src);
654             long duration = src.mDuration;
655             if (src.mNesting > 0) {
656                 duration += now - src.mStartUptime;
657             }
658             cont.mTotalTime = duration;
659             cont.mActiveTime = dumpTime(null, null, src, totalTime, now, false, false);
660             if (cont.mActiveTime < 0) {
661                 cont.mActiveTime = -cont.mActiveTime;
662             }
663             sources.add(new Pair<>(mSources.keyAt(isrc), cont));
664         }
665         Collections.sort(sources, ASSOCIATION_COMPARATOR);
666         return sources;
667     }
668 
dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix, ArrayList<Pair<SourceKey, SourceDumpContainer>> sources, long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll)669     public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix,
670             ArrayList<Pair<SourceKey, SourceDumpContainer>> sources, long now, long totalTime,
671             String reqPackage, boolean dumpDetails, boolean dumpAll) {
672         final String prefixInnerInner = prefixInner + "     ";
673         long totalDuration = mTotalActiveDuration;
674         if (mTotalActiveNesting > 0) {
675             totalDuration += now - mTotalActiveStartUptime;
676         }
677         if (totalDuration > 0 || mTotalActiveCount != 0) {
678             pw.print(prefix);
679             pw.print("Active count ");
680             pw.print(mTotalActiveCount);
681             if (dumpAll) {
682                 pw.print(": ");
683                 TimeUtils.formatDuration(totalDuration, pw);
684                 pw.print(" / ");
685             } else {
686                 pw.print(": time ");
687             }
688             DumpUtils.printPercent(pw, (double) totalDuration / (double) totalTime);
689             pw.println();
690         }
691         if (dumpAll && mTotalActiveNesting != 0) {
692             pw.print(prefix);
693             pw.print("mTotalActiveNesting=");
694             pw.print(mTotalActiveNesting);
695             pw.print(" mTotalActiveStartUptime=");
696             TimeUtils.formatDuration(mTotalActiveStartUptime, now, pw);
697             pw.println();
698         }
699         totalDuration = mTotalDuration;
700         if (mTotalNesting > 0) {
701             totalDuration += now - mTotalStartUptime;
702         }
703         if (totalDuration > 0 || mTotalCount != 0) {
704             pw.print(prefix);
705             pw.print("Total count ");
706             pw.print(mTotalCount);
707             if (dumpAll) {
708                 pw.print(": ");
709                 TimeUtils.formatDuration(totalDuration, pw);
710                 pw.print(" / ");
711             } else {
712                 pw.print(": time ");
713             }
714             DumpUtils.printPercent(pw, (double) totalDuration / (double) totalTime);
715             pw.println();
716         }
717         if (dumpAll && mTotalNesting != 0) {
718             pw.print(prefix);
719             pw.print("mTotalNesting=");
720             pw.print(mTotalNesting);
721             pw.print(" mTotalStartUptime=");
722             TimeUtils.formatDuration(mTotalStartUptime, now, pw);
723             pw.println();
724         }
725         final int NSRC = sources.size();
726         for (int isrc = 0; isrc < NSRC; isrc++) {
727             final SourceKey key = sources.get(isrc).first;
728             final SourceDumpContainer cont = sources.get(isrc).second;
729             final SourceState src = cont.mState;
730             pw.print(prefix);
731             pw.print("<- ");
732             pw.print(key.mProcess);
733             pw.print("/");
734             UserHandle.formatUid(pw, key.mUid);
735             if (key.mPackage != null) {
736                 pw.print(" (");
737                 pw.print(key.mPackage);
738                 pw.print(")");
739             }
740             // If we are skipping this one, we still print the first line just to give
741             // context for the others (so it is clear the total times for the overall
742             // association come from other sources whose times are not shown).
743             if (reqPackage != null && !reqPackage.equals(key.mProcess)
744                     && !reqPackage.equals(key.mPackage)) {
745                 pw.println();
746                 continue;
747             }
748             pw.println(":");
749             if (src.mActiveCount != 0 || src.mActiveDurations != null || src.mActiveDuration != 0
750                     || src.mActiveStartUptime != 0) {
751                 pw.print(prefixInner);
752                 pw.print("   Active count ");
753                 pw.print(src.mActiveCount);
754                 if (dumpDetails) {
755                     if (dumpAll) {
756                         if (src.mActiveDurations != null) {
757                             pw.print(" (multi-state)");
758                         } else if (src.mActiveProcState >= ProcessStats.STATE_PERSISTENT) {
759                             pw.print(" (");
760                             pw.print(DumpUtils.STATE_NAMES[src.mActiveProcState]);
761                             pw.print(")");
762                         } else {
763                             pw.print(" (*UNKNOWN STATE*)");
764                         }
765                     }
766                     if (dumpAll) {
767                         pw.print(": ");
768                         TimeUtils.formatDuration(cont.mActiveTime, pw);
769                         pw.print(" / ");
770                     } else {
771                         pw.print(": time ");
772                     }
773                     DumpUtils.printPercent(pw, (double) cont.mActiveTime / (double) totalTime);
774                     if (src.mActiveStartUptime != 0) {
775                         pw.print(" (running)");
776                     }
777                     pw.println();
778                     if (src.mActiveDurations != null) {
779                         dumpTime(pw, prefixInnerInner, src, totalTime, now, dumpDetails, dumpAll);
780                     }
781                 } else {
782                     pw.print(": ");
783                     dumpActiveDurationSummary(pw, src, totalTime, now, dumpAll);
784                 }
785             }
786             pw.print(prefixInner);
787             pw.print("   Total count ");
788             pw.print(src.mCount);
789             if (dumpAll) {
790                 pw.print(": ");
791                 TimeUtils.formatDuration(cont.mTotalTime, pw);
792                 pw.print(" / ");
793             } else {
794                 pw.print(": time ");
795             }
796             DumpUtils.printPercent(pw, (double) cont.mTotalTime / (double) totalTime);
797             if (src.mNesting > 0) {
798                 pw.print(" (running");
799                 if (dumpAll) {
800                     pw.print(" nest=");
801                     pw.print(src.mNesting);
802                 }
803                 if (src.mProcState != ProcessStats.STATE_NOTHING) {
804                     pw.print(" / ");
805                     pw.print(DumpUtils.STATE_NAMES[src.mProcState]);
806                     pw.print(" #");
807                     pw.print(src.mProcStateSeq);
808                 }
809                 pw.print(")");
810             }
811             pw.println();
812             if (dumpAll) {
813                 if (src.mInTrackingList) {
814                     pw.print(prefixInner);
815                     pw.print("   mInTrackingList=");
816                     pw.println(src.mInTrackingList);
817                 }
818                 if (src.mProcState != ProcessStats.STATE_NOTHING) {
819                     pw.print(prefixInner);
820                     pw.print("   mProcState=");
821                     pw.print(DumpUtils.STATE_NAMES[src.mProcState]);
822                     pw.print(" mProcStateSeq=");
823                     pw.println(src.mProcStateSeq);
824                 }
825             }
826         }
827     }
828 
dumpActiveDurationSummary(PrintWriter pw, final SourceState src, long totalTime, long now, boolean dumpAll)829     void dumpActiveDurationSummary(PrintWriter pw, final SourceState src, long totalTime,
830             long now, boolean dumpAll) {
831         long duration = dumpTime(null, null, src, totalTime, now, false, false);
832         final boolean isRunning = duration < 0;
833         if (isRunning) {
834             duration = -duration;
835         }
836         if (dumpAll) {
837             TimeUtils.formatDuration(duration, pw);
838             pw.print(" / ");
839         } else {
840             pw.print("time ");
841         }
842         DumpUtils.printPercent(pw, (double) duration / (double) totalTime);
843         if (src.mActiveStartUptime > 0) {
844             pw.print(" (running)");
845         }
846         pw.println();
847     }
848 
dumpTime(PrintWriter pw, String prefix, final SourceState src, long overallTime, long now, boolean dumpDetails, boolean dumpAll)849     long dumpTime(PrintWriter pw, String prefix, final SourceState src, long overallTime, long now,
850             boolean dumpDetails, boolean dumpAll) {
851         long totalTime = 0;
852         boolean isRunning = false;
853         for (int iprocstate = 0; iprocstate < ProcessStats.STATE_COUNT; iprocstate++) {
854             long time;
855             if (src.mActiveDurations != null) {
856                 time = src.mActiveDurations.getValueForId((byte) iprocstate);
857             } else {
858                 time = src.mActiveProcState == iprocstate ? src.mActiveDuration : 0;
859             }
860             final String running;
861             if (src.mActiveStartUptime != 0 && src.mActiveProcState == iprocstate) {
862                 running = " (running)";
863                 isRunning = true;
864                 time += now - src.mActiveStartUptime;
865             } else {
866                 running = null;
867             }
868             if (time != 0) {
869                 if (pw != null) {
870                     pw.print(prefix);
871                     pw.print(DumpUtils.STATE_LABELS[iprocstate]);
872                     pw.print(": ");
873                     if (dumpAll) {
874                         TimeUtils.formatDuration(time, pw);
875                         pw.print(" / ");
876                     } else {
877                         pw.print("time ");
878                     }
879                     DumpUtils.printPercent(pw, (double) time / (double) overallTime);
880                     if (running != null) {
881                         pw.print(running);
882                     }
883                     pw.println();
884                 }
885                 totalTime += time;
886             }
887         }
888         return isRunning ? -totalTime : totalTime;
889     }
890 
dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers, String associationName, long now)891     public void dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers,
892             String associationName, long now) {
893         final int NSRC = mSources.size();
894         for (int isrc = 0; isrc < NSRC; isrc++) {
895             final SourceKey key = mSources.keyAt(isrc);
896             final SourceState src = mSources.valueAt(isrc);
897             pw.print("pkgasc");
898             pw.print(",");
899             pw.print(pkgName);
900             pw.print(",");
901             pw.print(uid);
902             pw.print(",");
903             pw.print(vers);
904             pw.print(",");
905             pw.print(associationName);
906             pw.print(",");
907             pw.print(key.mProcess);
908             pw.print(",");
909             pw.print(key.mUid);
910             pw.print(",");
911             pw.print(src.mCount);
912             long duration = src.mDuration;
913             if (src.mNesting > 0) {
914                 duration += now - src.mStartUptime;
915             }
916             pw.print(",");
917             pw.print(duration);
918             pw.print(",");
919             pw.print(src.mActiveCount);
920             final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0;
921             if (src.mActiveDurations != null) {
922                 final int N = src.mActiveDurations.getKeyCount();
923                 for (int i=0; i<N; i++) {
924                     final int dkey = src.mActiveDurations.getKeyAt(i);
925                     duration = src.mActiveDurations.getValue(dkey);
926                     if (dkey == src.mActiveProcState) {
927                         duration += timeNow;
928                     }
929                     final int procState = SparseMappingTable.getIdFromKey(dkey);
930                     pw.print(",");
931                     DumpUtils.printArrayEntry(pw, DumpUtils.STATE_TAGS,  procState, 1);
932                     pw.print(':');
933                     pw.print(duration);
934                 }
935             } else {
936                 duration = src.mActiveDuration + timeNow;
937                 if (duration != 0) {
938                     pw.print(",");
939                     DumpUtils.printArrayEntry(pw, DumpUtils.STATE_TAGS,  src.mActiveProcState, 1);
940                     pw.print(':');
941                     pw.print(duration);
942                 }
943             }
944             pw.println();
945         }
946     }
947 
dumpDebug(ProtoOutputStream proto, long fieldId, long now)948     public void dumpDebug(ProtoOutputStream proto, long fieldId, long now) {
949         final long token = proto.start(fieldId);
950 
951         proto.write(PackageAssociationProcessStatsProto.COMPONENT_NAME, mName);
952 
953         proto.write(PackageAssociationProcessStatsProto.TOTAL_COUNT, mTotalCount);
954         proto.write(PackageAssociationProcessStatsProto.TOTAL_DURATION_MS, getTotalDuration(now));
955         if (mTotalActiveCount != 0) {
956             proto.write(PackageAssociationProcessStatsProto.ACTIVE_COUNT, mTotalActiveCount);
957             proto.write(PackageAssociationProcessStatsProto.ACTIVE_DURATION_MS,
958                     getActiveDuration(now));
959         }
960 
961         final int NSRC = mSources.size();
962         for (int isrc = 0; isrc < NSRC; isrc++) {
963             final SourceKey key = mSources.keyAt(isrc);
964             final SourceState src = mSources.valueAt(isrc);
965             final long sourceToken = proto.start(PackageAssociationProcessStatsProto.SOURCES);
966             proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_NAME, key.mProcess);
967             proto.write(PackageAssociationSourceProcessStatsProto.PACKAGE_NAME, key.mPackage);
968             proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_UID, key.mUid);
969             proto.write(PackageAssociationSourceProcessStatsProto.TOTAL_COUNT, src.mCount);
970             long duration = src.mDuration;
971             if (src.mNesting > 0) {
972                 duration += now - src.mStartUptime;
973             }
974             proto.write(PackageAssociationSourceProcessStatsProto.TOTAL_DURATION_MS, duration);
975             if (src.mActiveCount != 0) {
976                 proto.write(PackageAssociationSourceProcessStatsProto.ACTIVE_COUNT,
977                         src.mActiveCount);
978             }
979             final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0;
980             if (src.mActiveDurations != null) {
981                 final int N = src.mActiveDurations.getKeyCount();
982                 for (int i=0; i<N; i++) {
983                     final int dkey = src.mActiveDurations.getKeyAt(i);
984                     duration = src.mActiveDurations.getValue(dkey);
985                     if (dkey == src.mActiveProcState) {
986                         duration += timeNow;
987                     }
988                     final int procState = SparseMappingTable.getIdFromKey(dkey);
989                     final long stateToken = proto.start(
990                             PackageAssociationSourceProcessStatsProto.ACTIVE_STATE_STATS);
991                     DumpUtils.printProto(proto,
992                             PackageAssociationSourceProcessStatsProto.StateStats.PROCESS_STATE,
993                             DumpUtils.STATE_PROTO_ENUMS, procState, 1);
994                     proto.write(PackageAssociationSourceProcessStatsProto.StateStats.DURATION_MS,
995                             duration);
996                     proto.end(stateToken);
997                 }
998             } else {
999                 duration = src.mActiveDuration + timeNow;
1000                 if (duration != 0) {
1001                     final long stateToken = proto.start(
1002                             PackageAssociationSourceProcessStatsProto.ACTIVE_STATE_STATS);
1003                     DumpUtils.printProto(proto,
1004                             PackageAssociationSourceProcessStatsProto.StateStats.PROCESS_STATE,
1005                             DumpUtils.STATE_PROTO_ENUMS, src.mActiveProcState, 1);
1006                     proto.write(PackageAssociationSourceProcessStatsProto.StateStats.DURATION_MS,
1007                             duration);
1008                     proto.end(stateToken);
1009                 }
1010             }
1011             proto.end(sourceToken);
1012         }
1013 
1014         proto.end(token);
1015     }
1016 
toString()1017     public String toString() {
1018         return "AssociationState{" + Integer.toHexString(System.identityHashCode(this))
1019                 + " " + mName + " pkg=" + mPackageState.mPackageName + " proc="
1020                 + Integer.toHexString(System.identityHashCode(mProc)) + "}";
1021     }
1022 }
1023