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