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 
20 import android.annotation.Nullable;
21 import android.os.Parcel;
22 import android.os.SystemClock;
23 import android.os.UserHandle;
24 import android.service.procstats.PackageAssociationProcessStatsProto;
25 import android.service.procstats.PackageAssociationSourceProcessStatsProto;
26 import android.util.ArrayMap;
27 import android.util.Slog;
28 import android.util.TimeUtils;
29 import android.util.proto.ProtoOutputStream;
30 
31 import java.io.PrintWriter;
32 import java.util.ArrayList;
33 import java.util.Objects;
34 
35 public final class AssociationState {
36     private static final String TAG = "ProcessStats";
37     private static final boolean DEBUG = false;
38 
39     private final ProcessStats mProcessStats;
40     private final ProcessStats.PackageState mPackageState;
41     private final String mProcessName;
42     private final String mName;
43 
44     public final class SourceState {
45         final SourceKey mKey;
46         int mProcStateSeq = -1;
47         int mProcState = ProcessStats.STATE_NOTHING;
48         boolean mInTrackingList;
49         int mNesting;
50         int mCount;
51         long mStartUptime;
52         long mDuration;
53         long mTrackingUptime;
54         int mActiveCount;
55         int mActiveProcState = ProcessStats.STATE_NOTHING;
56         long mActiveStartUptime;
57         long mActiveDuration;
58         DurationsTable mDurations;
59 
SourceState(SourceKey key)60         SourceState(SourceKey key) {
61             mKey = key;
62         }
63 
getAssociationState()64         public AssociationState getAssociationState() {
65             return AssociationState.this;
66         }
67 
getProcessName()68         public String getProcessName() {
69             return mKey.mProcess;
70         }
71 
getUid()72         public int getUid() {
73             return mKey.mUid;
74         }
75 
trackProcState(int procState, int seq, long now)76         public void trackProcState(int procState, int seq, long now) {
77             procState = ProcessState.PROCESS_STATE_TO_STATE[procState];
78             if (seq != mProcStateSeq) {
79                 mProcStateSeq = seq;
80                 mProcState = procState;
81             } else if (procState < mProcState) {
82                 mProcState = procState;
83             }
84             if (procState < ProcessStats.STATE_HOME) {
85                 // If the proc state has become better than cached, then we want to
86                 // start tracking it to count when it is actually active.  If it drops
87                 // down to cached, we will clean it up when we later evaluate all currently
88                 // tracked associations in ProcessStats.updateTrackingAssociationsLocked().
89                 if (!mInTrackingList) {
90                     mInTrackingList = true;
91                     mTrackingUptime = now;
92                     mProcessStats.mTrackingAssociations.add(this);
93                 }
94             }
95         }
96 
stop()97         public void stop() {
98             mNesting--;
99             if (mNesting == 0) {
100                 mDuration += SystemClock.uptimeMillis() - mStartUptime;
101                 mNumActive--;
102                 stopTracking(SystemClock.uptimeMillis());
103             }
104         }
105 
startActive(long now)106         void startActive(long now) {
107             if (mInTrackingList) {
108                 if (mActiveStartUptime == 0) {
109                     mActiveStartUptime = now;
110                     mActiveCount++;
111                 }
112                 if (mActiveProcState != mProcState) {
113                     if (mActiveProcState != ProcessStats.STATE_NOTHING) {
114                         // Currently active proc state changed, need to store the duration
115                         // so far and switch tracking to the new proc state.
116                         final long duration = mActiveDuration + now - mActiveStartUptime;
117                         if (duration != 0) {
118                             if (mDurations == null) {
119                                 makeDurations();
120                             }
121                             mDurations.addDuration(mActiveProcState, duration);
122                             mActiveDuration = 0;
123                         }
124                         mActiveStartUptime = now;
125                     }
126                     mActiveProcState = mProcState;
127                 }
128             } else {
129                 Slog.wtf(TAG, "startActive while not tracking: " + this);
130             }
131         }
132 
stopActive(long now)133         void stopActive(long now) {
134             if (mActiveStartUptime != 0) {
135                 if (!mInTrackingList) {
136                     Slog.wtf(TAG, "stopActive while not tracking: " + this);
137                 }
138                 final long duration = mActiveDuration + now - mActiveStartUptime;
139                 if (mDurations != null) {
140                     mDurations.addDuration(mActiveProcState, duration);
141                 } else {
142                     mActiveDuration = duration;
143                 }
144                 mActiveStartUptime = 0;
145             }
146         }
147 
makeDurations()148         void makeDurations() {
149             mDurations = new DurationsTable(mProcessStats.mTableData);
150         }
151 
stopTracking(long now)152         void stopTracking(long now) {
153             stopActive(now);
154             if (mInTrackingList) {
155                 mInTrackingList = false;
156                 mProcState = ProcessStats.STATE_NOTHING;
157                 // Do a manual search for where to remove, since these objects will typically
158                 // be towards the end of the array.
159                 final ArrayList<SourceState> list = mProcessStats.mTrackingAssociations;
160                 for (int i = list.size() - 1; i >= 0; i--) {
161                     if (list.get(i) == this) {
162                         list.remove(i);
163                         return;
164                     }
165                 }
166                 Slog.wtf(TAG, "Stop tracking didn't find in tracking list: " + this);
167             }
168         }
169 
170         @Override
toString()171         public String toString() {
172             StringBuilder sb = new StringBuilder(64);
173             sb.append("SourceState{").append(Integer.toHexString(System.identityHashCode(this)))
174                     .append(" ").append(mKey.mProcess).append("/").append(mKey.mUid);
175             if (mProcState != ProcessStats.STATE_NOTHING) {
176                 sb.append(" ").append(DumpUtils.STATE_NAMES[mProcState]).append(" #")
177                         .append(mProcStateSeq);
178             }
179             sb.append("}");
180             return sb.toString();
181         }
182     }
183 
184     private final static class SourceKey {
185         /**
186          * UID, consider this final.  Not final just to avoid a temporary object during lookup.
187          */
188         int mUid;
189 
190         /**
191          * Process name, consider this final.  Not final just to avoid a temporary object during
192          * lookup.
193          */
194         String mProcess;
195 
196         /**
197          * Optional package name, or null; consider this final.  Not final just to avoid a
198          * temporary object during lookup.
199          */
200         @Nullable String mPackage;
201 
SourceKey(int uid, String process, String pkg)202         SourceKey(int uid, String process, String pkg) {
203             mUid = uid;
204             mProcess = process;
205             mPackage = pkg;
206         }
207 
equals(Object o)208         public boolean equals(Object o) {
209             if (!(o instanceof SourceKey)) {
210                 return false;
211             }
212             SourceKey s = (SourceKey) o;
213             return s.mUid == mUid && Objects.equals(s.mProcess, mProcess)
214                     && Objects.equals(s.mPackage, mPackage);
215         }
216 
217         @Override
hashCode()218         public int hashCode() {
219             return Integer.hashCode(mUid) ^ (mProcess == null ? 0 : mProcess.hashCode())
220                     ^ (mPackage == null ? 0 : (mPackage.hashCode() * 33));
221         }
222 
223         @Override
toString()224         public String toString() {
225             StringBuilder sb = new StringBuilder(64);
226             sb.append("SourceKey{");
227             UserHandle.formatUid(sb, mUid);
228             sb.append(' ');
229             sb.append(mProcess);
230             sb.append(' ');
231             sb.append(mPackage);
232             sb.append('}');
233             return sb.toString();
234         }
235     }
236 
237     /**
238      * All known sources for this target component...  uid -> process name -> source state.
239      */
240     private final ArrayMap<SourceKey, SourceState> mSources = new ArrayMap<>();
241 
242     private final SourceKey mTmpSourceKey = new SourceKey(0, null, null);
243 
244     private ProcessState mProc;
245 
246     private int mNumActive;
247 
AssociationState(ProcessStats processStats, ProcessStats.PackageState packageState, String name, String processName, ProcessState proc)248     public AssociationState(ProcessStats processStats, ProcessStats.PackageState packageState,
249             String name, String processName, ProcessState proc) {
250         mProcessStats = processStats;
251         mPackageState = packageState;
252         mName = name;
253         mProcessName = processName;
254         mProc = proc;
255     }
256 
getUid()257     public int getUid() {
258         return mPackageState.mUid;
259     }
260 
getPackage()261     public String getPackage() {
262         return mPackageState.mPackageName;
263     }
264 
getProcessName()265     public String getProcessName() {
266         return mProcessName;
267     }
268 
getName()269     public String getName() {
270         return mName;
271     }
272 
getProcess()273     public ProcessState getProcess() {
274         return mProc;
275     }
276 
setProcess(ProcessState proc)277     public void setProcess(ProcessState proc) {
278         mProc = proc;
279     }
280 
startSource(int uid, String processName, String packageName)281     public SourceState startSource(int uid, String processName, String packageName) {
282         mTmpSourceKey.mUid = uid;
283         mTmpSourceKey.mProcess = processName;
284         mTmpSourceKey.mPackage = packageName;
285         SourceState src = mSources.get(mTmpSourceKey);
286         if (src == null) {
287             SourceKey key = new SourceKey(uid, processName, packageName);
288             src = new SourceState(key);
289             mSources.put(key, src);
290         }
291         src.mNesting++;
292         if (src.mNesting == 1) {
293             src.mCount++;
294             src.mStartUptime = SystemClock.uptimeMillis();
295             mNumActive++;
296         }
297         return src;
298     }
299 
add(AssociationState other)300     public void add(AssociationState other) {
301         for (int isrc = other.mSources.size() - 1; isrc >= 0; isrc--) {
302             final SourceKey key = other.mSources.keyAt(isrc);
303             final SourceState otherSrc = other.mSources.valueAt(isrc);
304             SourceState mySrc = mSources.get(key);
305             if (mySrc == null) {
306                 mySrc = new SourceState(key);
307                 mSources.put(key, mySrc);
308             }
309             mySrc.mCount += otherSrc.mCount;
310             mySrc.mDuration += otherSrc.mDuration;
311             mySrc.mActiveCount += otherSrc.mActiveCount;
312             if (otherSrc.mActiveDuration != 0 || otherSrc.mDurations != null) {
313                 // Only need to do anything if the other one has some duration data.
314                 if (mySrc.mDurations != null) {
315                     // If the target already has multiple durations, just add in whatever
316                     // we have in the other.
317                     if (otherSrc.mDurations != null) {
318                         mySrc.mDurations.addDurations(otherSrc.mDurations);
319                     } else {
320                         mySrc.mDurations.addDuration(otherSrc.mActiveProcState,
321                                 otherSrc.mActiveDuration);
322                     }
323                 } else if (otherSrc.mDurations != null) {
324                     // The other one has multiple durations, but we don't.  Expand to
325                     // multiple durations and copy over.
326                     mySrc.makeDurations();
327                     mySrc.mDurations.addDurations(otherSrc.mDurations);
328                     if (mySrc.mActiveDuration != 0) {
329                         mySrc.mDurations.addDuration(mySrc.mActiveProcState, mySrc.mActiveDuration);
330                         mySrc.mActiveDuration = 0;
331                         mySrc.mActiveProcState = ProcessStats.STATE_NOTHING;
332                     }
333                 } else if (mySrc.mActiveDuration != 0) {
334                     // Both have a single inline duration...  we can either add them together,
335                     // or need to expand to multiple durations.
336                     if (mySrc.mActiveProcState == otherSrc.mActiveProcState) {
337                         mySrc.mDuration += otherSrc.mDuration;
338                     } else {
339                         // The two have durations with different proc states, need to turn
340                         // in to multiple durations.
341                         mySrc.makeDurations();
342                         mySrc.mDurations.addDuration(mySrc.mActiveProcState, mySrc.mActiveDuration);
343                         mySrc.mDurations.addDuration(otherSrc.mActiveProcState,
344                                 otherSrc.mActiveDuration);
345                         mySrc.mActiveDuration = 0;
346                         mySrc.mActiveProcState = ProcessStats.STATE_NOTHING;
347                     }
348                 } else {
349                     // The other one has a duration, and we know the target doesn't.  Copy over.
350                     mySrc.mActiveProcState = otherSrc.mActiveProcState;
351                     mySrc.mActiveDuration = otherSrc.mActiveDuration;
352                 }
353             }
354         }
355     }
356 
isInUse()357     public boolean isInUse() {
358         return mNumActive > 0;
359     }
360 
resetSafely(long now)361     public void resetSafely(long now) {
362         if (!isInUse()) {
363             mSources.clear();
364         } else {
365             // We have some active sources...  clear out everything but those.
366             for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) {
367                 SourceState src = mSources.valueAt(isrc);
368                 if (src.mNesting > 0) {
369                     src.mCount = 1;
370                     src.mStartUptime = now;
371                     src.mDuration = 0;
372                     if (src.mActiveStartUptime > 0) {
373                         src.mActiveCount = 1;
374                         src.mActiveStartUptime = now;
375                     } else {
376                         src.mActiveCount = 0;
377                     }
378                     src.mActiveDuration = 0;
379                     src.mDurations = null;
380                 } else {
381                     mSources.removeAt(isrc);
382                 }
383             }
384         }
385     }
386 
writeToParcel(ProcessStats stats, Parcel out, long nowUptime)387     public void writeToParcel(ProcessStats stats, Parcel out, long nowUptime) {
388         final int NSRC = mSources.size();
389         out.writeInt(NSRC);
390         for (int isrc = 0; isrc < NSRC; isrc++) {
391             final SourceKey key = mSources.keyAt(isrc);
392             final SourceState src = mSources.valueAt(isrc);
393             out.writeInt(key.mUid);
394             stats.writeCommonString(out, key.mProcess);
395             stats.writeCommonString(out, key.mPackage);
396             out.writeInt(src.mCount);
397             out.writeLong(src.mDuration);
398             out.writeInt(src.mActiveCount);
399             if (src.mDurations != null) {
400                 out.writeInt(1);
401                 src.mDurations.writeToParcel(out);
402             } else {
403                 out.writeInt(0);
404                 out.writeInt(src.mActiveProcState);
405                 out.writeLong(src.mActiveDuration);
406             }
407         }
408     }
409 
410     /**
411      * Returns non-null if all else fine, else a String that describes the error that
412      * caused it to fail.
413      */
readFromParcel(ProcessStats stats, Parcel in, int parcelVersion)414     public String readFromParcel(ProcessStats stats, Parcel in, int parcelVersion) {
415         final int NSRC = in.readInt();
416         if (NSRC < 0 || NSRC > 100000) {
417             return "Association with bad src count: " + NSRC;
418         }
419         for (int isrc = 0; isrc < NSRC; isrc++) {
420             final int uid = in.readInt();
421             final String procName = stats.readCommonString(in, parcelVersion);
422             final String pkgName = stats.readCommonString(in, parcelVersion);
423             final SourceKey key = new SourceKey(uid, procName, pkgName);
424             final SourceState src = new SourceState(key);
425             src.mCount = in.readInt();
426             src.mDuration = in.readLong();
427             src.mActiveCount = in.readInt();
428             if (in.readInt() != 0) {
429                 src.makeDurations();
430                 if (!src.mDurations.readFromParcel(in)) {
431                     return "Duration table corrupt: " + key + " <- " + src;
432                 }
433             } else {
434                 src.mActiveProcState = in.readInt();
435                 src.mActiveDuration = in.readLong();
436             }
437             mSources.put(key, src);
438         }
439         return null;
440     }
441 
commitStateTime(long nowUptime)442     public void commitStateTime(long nowUptime) {
443         if (isInUse()) {
444             for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) {
445                 SourceState src = mSources.valueAt(isrc);
446                 if (src.mNesting > 0) {
447                     src.mDuration += nowUptime - src.mStartUptime;
448                     src.mStartUptime = nowUptime;
449                 }
450                 if (src.mActiveStartUptime > 0) {
451                     final long duration = src.mActiveDuration + nowUptime - src.mActiveStartUptime;
452                     if (src.mDurations != null) {
453                         src.mDurations.addDuration(src.mActiveProcState, duration);
454                     } else {
455                         src.mActiveDuration = duration;
456                     }
457                     src.mActiveStartUptime = nowUptime;
458                 }
459             }
460         }
461     }
462 
hasProcessOrPackage(String procName)463     public boolean hasProcessOrPackage(String procName) {
464         final int NSRC = mSources.size();
465         for (int isrc = 0; isrc < NSRC; isrc++) {
466             final SourceKey key = mSources.keyAt(isrc);
467             if (procName.equals(key.mProcess) || procName.equals(key.mPackage)) {
468                 return true;
469             }
470         }
471         return false;
472     }
473 
dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix, long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll)474     public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix,
475             long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll) {
476         if (dumpAll) {
477             pw.print(prefix);
478             pw.print("mNumActive=");
479             pw.println(mNumActive);
480         }
481         final int NSRC = mSources.size();
482         for (int isrc = 0; isrc < NSRC; isrc++) {
483             final SourceKey key = mSources.keyAt(isrc);
484             final SourceState src = mSources.valueAt(isrc);
485             if (reqPackage != null && !reqPackage.equals(key.mProcess)
486                     && !reqPackage.equals(key.mPackage)) {
487                 continue;
488             }
489             pw.print(prefixInner);
490             pw.print("<- ");
491             pw.print(key.mProcess);
492             pw.print("/");
493             UserHandle.formatUid(pw, key.mUid);
494             if (key.mPackage != null) {
495                 pw.print(" (");
496                 pw.print(key.mPackage);
497                 pw.print(")");
498             }
499             pw.println(":");
500             pw.print(prefixInner);
501             pw.print("   Total count ");
502             pw.print(src.mCount);
503             long duration = src.mDuration;
504             if (src.mNesting > 0) {
505                 duration += now - src.mStartUptime;
506             }
507             if (dumpAll) {
508                 pw.print(": Duration ");
509                 TimeUtils.formatDuration(duration, pw);
510                 pw.print(" / ");
511             } else {
512                 pw.print(": time ");
513             }
514             DumpUtils.printPercent(pw, (double)duration/(double)totalTime);
515             if (src.mNesting > 0) {
516                 pw.print(" (running");
517                 if (src.mProcState != ProcessStats.STATE_NOTHING) {
518                     pw.print(" / ");
519                     pw.print(DumpUtils.STATE_NAMES[src.mProcState]);
520                     pw.print(" #");
521                     pw.print(src.mProcStateSeq);
522                 }
523                 pw.print(")");
524             }
525             pw.println();
526             if (src.mActiveCount > 0 || src.mDurations != null || src.mActiveDuration != 0
527                     || src.mActiveStartUptime != 0) {
528                 pw.print(prefixInner);
529                 pw.print("   Active count ");
530                 pw.print(src.mActiveCount);
531                 if (dumpDetails) {
532                     if (dumpAll) {
533                         pw.print(src.mDurations != null ? " (multi-field)" : " (inline)");
534                     }
535                     pw.println(":");
536                     dumpTime(pw, prefixInner, src, totalTime, now, dumpDetails, dumpAll);
537                 } else {
538                     pw.print(": ");
539                     dumpActiveDurationSummary(pw, src, totalTime, now, dumpAll);
540                     pw.println();
541                 }
542             }
543             if (dumpAll) {
544                 if (src.mInTrackingList) {
545                     pw.print(prefixInner);
546                     pw.print("   mInTrackingList=");
547                     pw.println(src.mInTrackingList);
548                 }
549                 if (src.mProcState != ProcessStats.STATE_NOTHING) {
550                     pw.print(prefixInner);
551                     pw.print("   mProcState=");
552                     pw.print(DumpUtils.STATE_NAMES[src.mProcState]);
553                     pw.print(" mProcStateSeq=");
554                     pw.println(src.mProcStateSeq);
555                 }
556             }
557         }
558     }
559 
dumpActiveDurationSummary(PrintWriter pw, final SourceState src, long totalTime, long now, boolean dumpAll)560     void dumpActiveDurationSummary(PrintWriter pw, final SourceState src, long totalTime,
561             long now, boolean dumpAll) {
562         long duration = dumpTime(null, null, src, totalTime, now, false, false);
563         final boolean isRunning = duration < 0;
564         if (isRunning) {
565             duration = -duration;
566         }
567         if (dumpAll) {
568             pw.print("Duration ");
569             TimeUtils.formatDuration(duration, pw);
570             pw.print(" / ");
571         } else {
572             pw.print("time ");
573         }
574         DumpUtils.printPercent(pw, (double) duration / (double) totalTime);
575         if (src.mActiveStartUptime > 0) {
576             pw.print(" (running)");
577         }
578         pw.println();
579     }
580 
dumpTime(PrintWriter pw, String prefix, final SourceState src, long overallTime, long now, boolean dumpDetails, boolean dumpAll)581     long dumpTime(PrintWriter pw, String prefix, final SourceState src, long overallTime, long now,
582             boolean dumpDetails, boolean dumpAll) {
583         long totalTime = 0;
584         boolean isRunning = false;
585         for (int iprocstate = 0; iprocstate < ProcessStats.STATE_COUNT; iprocstate++) {
586             long time;
587             if (src.mDurations != null) {
588                 time = src.mDurations.getValueForId((byte)iprocstate);
589             } else {
590                 time = src.mActiveProcState == iprocstate ? src.mDuration : 0;
591             }
592             final String running;
593             if (src.mActiveStartUptime != 0 && src.mActiveProcState == iprocstate) {
594                 running = " (running)";
595                 isRunning = true;
596                 time += now - src.mActiveStartUptime;
597             } else {
598                 running = null;
599             }
600             if (time != 0) {
601                 if (pw != null) {
602                     pw.print(prefix);
603                     pw.print("  ");
604                     pw.print(DumpUtils.STATE_LABELS[iprocstate]);
605                     pw.print(": ");
606                     if (dumpAll) {
607                         pw.print("Duration ");
608                         TimeUtils.formatDuration(time, pw);
609                         pw.print(" / ");
610                     } else {
611                         pw.print("time ");
612                     }
613                     DumpUtils.printPercent(pw, (double) time / (double) overallTime);
614                     if (running != null) {
615                         pw.print(running);
616                     }
617                     pw.println();
618                 }
619                 totalTime += time;
620             }
621         }
622         if (totalTime != 0 && pw != null) {
623             pw.print(prefix);
624             pw.print("  ");
625             pw.print(DumpUtils.STATE_LABEL_TOTAL);
626             pw.print(": ");
627             if (dumpAll) {
628                 pw.print("Duration ");
629                 TimeUtils.formatDuration(totalTime, pw);
630                 pw.print(" / ");
631             } else {
632                 pw.print("time ");
633             }
634             DumpUtils.printPercent(pw, (double) totalTime / (double) overallTime);
635             pw.println();
636         }
637         return isRunning ? -totalTime : totalTime;
638     }
639 
dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers, String associationName, long now)640     public void dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers,
641             String associationName, long now) {
642         final int NSRC = mSources.size();
643         for (int isrc = 0; isrc < NSRC; isrc++) {
644             final SourceKey key = mSources.keyAt(isrc);
645             final SourceState src = mSources.valueAt(isrc);
646             pw.print("pkgasc");
647             pw.print(",");
648             pw.print(pkgName);
649             pw.print(",");
650             pw.print(uid);
651             pw.print(",");
652             pw.print(vers);
653             pw.print(",");
654             pw.print(associationName);
655             pw.print(",");
656             pw.print(key.mProcess);
657             pw.print(",");
658             pw.print(key.mUid);
659             pw.print(",");
660             pw.print(src.mCount);
661             long duration = src.mDuration;
662             if (src.mNesting > 0) {
663                 duration += now - src.mStartUptime;
664             }
665             pw.print(",");
666             pw.print(duration);
667             pw.print(",");
668             pw.print(src.mActiveCount);
669             final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0;
670             if (src.mDurations != null) {
671                 final int N = src.mDurations.getKeyCount();
672                 for (int i=0; i<N; i++) {
673                     final int dkey = src.mDurations.getKeyAt(i);
674                     duration = src.mDurations.getValue(dkey);
675                     if (dkey == src.mActiveProcState) {
676                         duration += timeNow;
677                     }
678                     final int procState = SparseMappingTable.getIdFromKey(dkey);
679                     pw.print(",");
680                     DumpUtils.printArrayEntry(pw, DumpUtils.STATE_TAGS,  procState, 1);
681                     pw.print(':');
682                     pw.print(duration);
683                 }
684             } else {
685                 duration = src.mActiveDuration + timeNow;
686                 if (duration != 0) {
687                     pw.print(",");
688                     DumpUtils.printArrayEntry(pw, DumpUtils.STATE_TAGS,  src.mActiveProcState, 1);
689                     pw.print(':');
690                     pw.print(duration);
691                 }
692             }
693             pw.println();
694         }
695     }
696 
writeToProto(ProtoOutputStream proto, long fieldId, long now)697     public void writeToProto(ProtoOutputStream proto, long fieldId, long now) {
698         final long token = proto.start(fieldId);
699 
700         proto.write(PackageAssociationProcessStatsProto.COMPONENT_NAME, mName);
701 
702         final int NSRC = mSources.size();
703         for (int isrc = 0; isrc < NSRC; isrc++) {
704             final SourceKey key = mSources.keyAt(isrc);
705             final SourceState src = mSources.valueAt(isrc);
706             final long sourceToken = proto.start(PackageAssociationProcessStatsProto.SOURCES);
707             proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_NAME, key.mProcess);
708             proto.write(PackageAssociationSourceProcessStatsProto.PACKAGE_NAME, key.mPackage);
709             proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_UID, key.mUid);
710             proto.write(PackageAssociationSourceProcessStatsProto.TOTAL_COUNT, src.mCount);
711             long duration = src.mDuration;
712             if (src.mNesting > 0) {
713                 duration += now - src.mStartUptime;
714             }
715             proto.write(PackageAssociationSourceProcessStatsProto.TOTAL_DURATION_MS, duration);
716             if (src.mActiveCount != 0) {
717                 proto.write(PackageAssociationSourceProcessStatsProto.ACTIVE_COUNT,
718                         src.mActiveCount);
719             }
720             final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0;
721             if (src.mDurations != null) {
722                 final int N = src.mDurations.getKeyCount();
723                 for (int i=0; i<N; i++) {
724                     final int dkey = src.mDurations.getKeyAt(i);
725                     duration = src.mDurations.getValue(dkey);
726                     if (dkey == src.mActiveProcState) {
727                         duration += timeNow;
728                     }
729                     final int procState = SparseMappingTable.getIdFromKey(dkey);
730                     final long stateToken = proto.start(
731                             PackageAssociationSourceProcessStatsProto.ACTIVE_STATE_STATS);
732                     DumpUtils.printProto(proto,
733                             PackageAssociationSourceProcessStatsProto.StateStats.PROCESS_STATE,
734                             DumpUtils.STATE_PROTO_ENUMS, procState, 1);
735                     proto.write(PackageAssociationSourceProcessStatsProto.StateStats.DURATION_MS,
736                             duration);
737                     proto.end(stateToken);
738                 }
739             } else {
740                 duration = src.mActiveDuration + timeNow;
741                 if (duration != 0) {
742                     final long stateToken = proto.start(
743                             PackageAssociationSourceProcessStatsProto.ACTIVE_STATE_STATS);
744                     DumpUtils.printProto(proto,
745                             PackageAssociationSourceProcessStatsProto.StateStats.PROCESS_STATE,
746                             DumpUtils.STATE_PROTO_ENUMS, src.mActiveProcState, 1);
747                     proto.write(PackageAssociationSourceProcessStatsProto.StateStats.DURATION_MS,
748                             duration);
749                     proto.end(stateToken);
750                 }
751             }
752             proto.end(sourceToken);
753         }
754 
755         proto.end(token);
756     }
757 
toString()758     public String toString() {
759         return "AssociationState{" + Integer.toHexString(System.identityHashCode(this))
760                 + " " + mName + " pkg=" + mPackageState.mPackageName + " proc="
761                 + Integer.toHexString(System.identityHashCode(mProc)) + "}";
762     }
763 }
764