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