1 /*
2  * Copyright (C) 2016 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.server.job;
18 
19 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
20 import static com.android.server.job.JobSchedulerService.sSystemClock;
21 import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
22 
23 import android.app.job.JobInfo;
24 import android.app.job.JobParameters;
25 import android.os.UserHandle;
26 import android.text.format.DateFormat;
27 import android.util.ArrayMap;
28 import android.util.SparseArray;
29 import android.util.SparseIntArray;
30 import android.util.TimeUtils;
31 import android.util.proto.ProtoOutputStream;
32 
33 import com.android.internal.util.RingBufferIndices;
34 import com.android.server.job.controllers.JobStatus;
35 
36 import java.io.PrintWriter;
37 
38 public final class JobPackageTracker {
39     // We batch every 30 minutes.
40     static final long BATCHING_TIME = 30*60*1000;
41     // Number of historical data sets we keep.
42     static final int NUM_HISTORY = 5;
43 
44     private static final int EVENT_BUFFER_SIZE = 100;
45 
46     public static final int EVENT_CMD_MASK = 0xff;
47     public static final int EVENT_STOP_REASON_SHIFT = 8;
48     public static final int EVENT_STOP_REASON_MASK = 0xff << EVENT_STOP_REASON_SHIFT;
49     public static final int EVENT_NULL = 0;
50     public static final int EVENT_START_JOB = 1;
51     public static final int EVENT_STOP_JOB = 2;
52     public static final int EVENT_START_PERIODIC_JOB = 3;
53     public static final int EVENT_STOP_PERIODIC_JOB = 4;
54 
55     private final RingBufferIndices mEventIndices = new RingBufferIndices(EVENT_BUFFER_SIZE);
56     private final int[] mEventCmds = new int[EVENT_BUFFER_SIZE];
57     private final long[] mEventTimes = new long[EVENT_BUFFER_SIZE];
58     private final int[] mEventUids = new int[EVENT_BUFFER_SIZE];
59     private final String[] mEventTags = new String[EVENT_BUFFER_SIZE];
60     private final int[] mEventJobIds = new int[EVENT_BUFFER_SIZE];
61     private final String[] mEventReasons = new String[EVENT_BUFFER_SIZE];
62 
addEvent(int cmd, int uid, String tag, int jobId, int stopReason, String debugReason)63     public void addEvent(int cmd, int uid, String tag, int jobId, int stopReason,
64             String debugReason) {
65         int index = mEventIndices.add();
66         mEventCmds[index] = cmd | ((stopReason<<EVENT_STOP_REASON_SHIFT) & EVENT_STOP_REASON_MASK);
67         mEventTimes[index] = sElapsedRealtimeClock.millis();
68         mEventUids[index] = uid;
69         mEventTags[index] = tag;
70         mEventJobIds[index] = jobId;
71         mEventReasons[index] = debugReason;
72     }
73 
74     DataSet mCurDataSet = new DataSet();
75     DataSet[] mLastDataSets = new DataSet[NUM_HISTORY];
76 
77     final static class PackageEntry {
78         long pastActiveTime;
79         long activeStartTime;
80         int activeNesting;
81         int activeCount;
82         boolean hadActive;
83         long pastActiveTopTime;
84         long activeTopStartTime;
85         int activeTopNesting;
86         int activeTopCount;
87         boolean hadActiveTop;
88         long pastPendingTime;
89         long pendingStartTime;
90         int pendingNesting;
91         int pendingCount;
92         boolean hadPending;
93         final SparseIntArray stopReasons = new SparseIntArray();
94 
getActiveTime(long now)95         public long getActiveTime(long now) {
96             long time = pastActiveTime;
97             if (activeNesting > 0) {
98                 time += now - activeStartTime;
99             }
100             return time;
101         }
102 
getActiveTopTime(long now)103         public long getActiveTopTime(long now) {
104             long time = pastActiveTopTime;
105             if (activeTopNesting > 0) {
106                 time += now - activeTopStartTime;
107             }
108             return time;
109         }
110 
getPendingTime(long now)111         public long getPendingTime(long now) {
112             long time = pastPendingTime;
113             if (pendingNesting > 0) {
114                 time += now - pendingStartTime;
115             }
116             return time;
117         }
118     }
119 
120     final static class DataSet {
121         final SparseArray<ArrayMap<String, PackageEntry>> mEntries = new SparseArray<>();
122         final long mStartUptimeTime;
123         final long mStartElapsedTime;
124         final long mStartClockTime;
125         long mSummedTime;
126         int mMaxTotalActive;
127         int mMaxFgActive;
128 
DataSet(DataSet otherTimes)129         public DataSet(DataSet otherTimes) {
130             mStartUptimeTime = otherTimes.mStartUptimeTime;
131             mStartElapsedTime = otherTimes.mStartElapsedTime;
132             mStartClockTime = otherTimes.mStartClockTime;
133         }
134 
DataSet()135         public DataSet() {
136             mStartUptimeTime = sUptimeMillisClock.millis();
137             mStartElapsedTime = sElapsedRealtimeClock.millis();
138             mStartClockTime = sSystemClock.millis();
139         }
140 
getOrCreateEntry(int uid, String pkg)141         private PackageEntry getOrCreateEntry(int uid, String pkg) {
142             ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid);
143             if (uidMap == null) {
144                 uidMap = new ArrayMap<>();
145                 mEntries.put(uid, uidMap);
146             }
147             PackageEntry entry = uidMap.get(pkg);
148             if (entry == null) {
149                 entry = new PackageEntry();
150                 uidMap.put(pkg, entry);
151             }
152             return entry;
153         }
154 
getEntry(int uid, String pkg)155         public PackageEntry getEntry(int uid, String pkg) {
156             ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid);
157             if (uidMap == null) {
158                 return null;
159             }
160             return uidMap.get(pkg);
161         }
162 
getTotalTime(long now)163         long getTotalTime(long now) {
164             if (mSummedTime > 0) {
165                 return mSummedTime;
166             }
167             return now - mStartUptimeTime;
168         }
169 
incPending(int uid, String pkg, long now)170         void incPending(int uid, String pkg, long now) {
171             PackageEntry pe = getOrCreateEntry(uid, pkg);
172             if (pe.pendingNesting == 0) {
173                 pe.pendingStartTime = now;
174                 pe.pendingCount++;
175             }
176             pe.pendingNesting++;
177         }
178 
decPending(int uid, String pkg, long now)179         void decPending(int uid, String pkg, long now) {
180             PackageEntry pe = getOrCreateEntry(uid, pkg);
181             if (pe.pendingNesting == 1) {
182                 pe.pastPendingTime += now - pe.pendingStartTime;
183             }
184             pe.pendingNesting--;
185         }
186 
incActive(int uid, String pkg, long now)187         void incActive(int uid, String pkg, long now) {
188             PackageEntry pe = getOrCreateEntry(uid, pkg);
189             if (pe.activeNesting == 0) {
190                 pe.activeStartTime = now;
191                 pe.activeCount++;
192             }
193             pe.activeNesting++;
194         }
195 
decActive(int uid, String pkg, long now, int stopReason)196         void decActive(int uid, String pkg, long now, int stopReason) {
197             PackageEntry pe = getOrCreateEntry(uid, pkg);
198             if (pe.activeNesting == 1) {
199                 pe.pastActiveTime += now - pe.activeStartTime;
200             }
201             pe.activeNesting--;
202             int count = pe.stopReasons.get(stopReason, 0);
203             pe.stopReasons.put(stopReason, count+1);
204         }
205 
incActiveTop(int uid, String pkg, long now)206         void incActiveTop(int uid, String pkg, long now) {
207             PackageEntry pe = getOrCreateEntry(uid, pkg);
208             if (pe.activeTopNesting == 0) {
209                 pe.activeTopStartTime = now;
210                 pe.activeTopCount++;
211             }
212             pe.activeTopNesting++;
213         }
214 
decActiveTop(int uid, String pkg, long now, int stopReason)215         void decActiveTop(int uid, String pkg, long now, int stopReason) {
216             PackageEntry pe = getOrCreateEntry(uid, pkg);
217             if (pe.activeTopNesting == 1) {
218                 pe.pastActiveTopTime += now - pe.activeTopStartTime;
219             }
220             pe.activeTopNesting--;
221             int count = pe.stopReasons.get(stopReason, 0);
222             pe.stopReasons.put(stopReason, count+1);
223         }
224 
finish(DataSet next, long now)225         void finish(DataSet next, long now) {
226             for (int i = mEntries.size() - 1; i >= 0; i--) {
227                 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
228                 for (int j = uidMap.size() - 1; j >= 0; j--) {
229                     PackageEntry pe = uidMap.valueAt(j);
230                     if (pe.activeNesting > 0 || pe.activeTopNesting > 0 || pe.pendingNesting > 0) {
231                         // Propagate existing activity in to next data set.
232                         PackageEntry nextPe = next.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j));
233                         nextPe.activeStartTime = now;
234                         nextPe.activeNesting = pe.activeNesting;
235                         nextPe.activeTopStartTime = now;
236                         nextPe.activeTopNesting = pe.activeTopNesting;
237                         nextPe.pendingStartTime = now;
238                         nextPe.pendingNesting = pe.pendingNesting;
239                         // Finish it off.
240                         if (pe.activeNesting > 0) {
241                             pe.pastActiveTime += now - pe.activeStartTime;
242                             pe.activeNesting = 0;
243                         }
244                         if (pe.activeTopNesting > 0) {
245                             pe.pastActiveTopTime += now - pe.activeTopStartTime;
246                             pe.activeTopNesting = 0;
247                         }
248                         if (pe.pendingNesting > 0) {
249                             pe.pastPendingTime += now - pe.pendingStartTime;
250                             pe.pendingNesting = 0;
251                         }
252                     }
253                 }
254             }
255         }
256 
addTo(DataSet out, long now)257         void addTo(DataSet out, long now) {
258             out.mSummedTime += getTotalTime(now);
259             for (int i = mEntries.size() - 1; i >= 0; i--) {
260                 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
261                 for (int j = uidMap.size() - 1; j >= 0; j--) {
262                     PackageEntry pe = uidMap.valueAt(j);
263                     PackageEntry outPe = out.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j));
264                     outPe.pastActiveTime += pe.pastActiveTime;
265                     outPe.activeCount += pe.activeCount;
266                     outPe.pastActiveTopTime += pe.pastActiveTopTime;
267                     outPe.activeTopCount += pe.activeTopCount;
268                     outPe.pastPendingTime += pe.pastPendingTime;
269                     outPe.pendingCount += pe.pendingCount;
270                     if (pe.activeNesting > 0) {
271                         outPe.pastActiveTime += now - pe.activeStartTime;
272                         outPe.hadActive = true;
273                     }
274                     if (pe.activeTopNesting > 0) {
275                         outPe.pastActiveTopTime += now - pe.activeTopStartTime;
276                         outPe.hadActiveTop = true;
277                     }
278                     if (pe.pendingNesting > 0) {
279                         outPe.pastPendingTime += now - pe.pendingStartTime;
280                         outPe.hadPending = true;
281                     }
282                     for (int k = pe.stopReasons.size()-1; k >= 0; k--) {
283                         int type = pe.stopReasons.keyAt(k);
284                         outPe.stopReasons.put(type, outPe.stopReasons.get(type, 0)
285                                 + pe.stopReasons.valueAt(k));
286                     }
287                 }
288             }
289             if (mMaxTotalActive > out.mMaxTotalActive) {
290                 out.mMaxTotalActive = mMaxTotalActive;
291             }
292             if (mMaxFgActive > out.mMaxFgActive) {
293                 out.mMaxFgActive = mMaxFgActive;
294             }
295         }
296 
printDuration(PrintWriter pw, long period, long duration, int count, String suffix)297         void printDuration(PrintWriter pw, long period, long duration, int count, String suffix) {
298             float fraction = duration / (float) period;
299             int percent = (int) ((fraction * 100) + .5f);
300             if (percent > 0) {
301                 pw.print(" ");
302                 pw.print(percent);
303                 pw.print("% ");
304                 pw.print(count);
305                 pw.print("x ");
306                 pw.print(suffix);
307             } else if (count > 0) {
308                 pw.print(" ");
309                 pw.print(count);
310                 pw.print("x ");
311                 pw.print(suffix);
312             }
313         }
314 
dump(PrintWriter pw, String header, String prefix, long now, long nowElapsed, int filterUid)315         void dump(PrintWriter pw, String header, String prefix, long now, long nowElapsed,
316                 int filterUid) {
317             final long period = getTotalTime(now);
318             pw.print(prefix); pw.print(header); pw.print(" at ");
319             pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString());
320             pw.print(" (");
321             TimeUtils.formatDuration(mStartElapsedTime, nowElapsed, pw);
322             pw.print(") over ");
323             TimeUtils.formatDuration(period, pw);
324             pw.println(":");
325             final int NE = mEntries.size();
326             for (int i = 0; i < NE; i++) {
327                 int uid = mEntries.keyAt(i);
328                 if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
329                     continue;
330                 }
331                 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
332                 final int NP = uidMap.size();
333                 for (int j = 0; j < NP; j++) {
334                     PackageEntry pe = uidMap.valueAt(j);
335                     pw.print(prefix); pw.print("  ");
336                     UserHandle.formatUid(pw, uid);
337                     pw.print(" / "); pw.print(uidMap.keyAt(j));
338                     pw.println(":");
339                     pw.print(prefix); pw.print("   ");
340                     printDuration(pw, period, pe.getPendingTime(now), pe.pendingCount, "pending");
341                     printDuration(pw, period, pe.getActiveTime(now), pe.activeCount, "active");
342                     printDuration(pw, period, pe.getActiveTopTime(now), pe.activeTopCount,
343                             "active-top");
344                     if (pe.pendingNesting > 0 || pe.hadPending) {
345                         pw.print(" (pending)");
346                     }
347                     if (pe.activeNesting > 0 || pe.hadActive) {
348                         pw.print(" (active)");
349                     }
350                     if (pe.activeTopNesting > 0 || pe.hadActiveTop) {
351                         pw.print(" (active-top)");
352                     }
353                     pw.println();
354                     if (pe.stopReasons.size() > 0) {
355                         pw.print(prefix); pw.print("    ");
356                         for (int k = 0; k < pe.stopReasons.size(); k++) {
357                             if (k > 0) {
358                                 pw.print(", ");
359                             }
360                             pw.print(pe.stopReasons.valueAt(k));
361                             pw.print("x ");
362                             pw.print(JobParameters
363                                     .getReasonCodeDescription(pe.stopReasons.keyAt(k)));
364                         }
365                         pw.println();
366                     }
367                 }
368             }
369             pw.print(prefix); pw.print("  Max concurrency: ");
370             pw.print(mMaxTotalActive); pw.print(" total, ");
371             pw.print(mMaxFgActive); pw.println(" foreground");
372         }
373 
printPackageEntryState(ProtoOutputStream proto, long fieldId, long duration, int count)374         private void printPackageEntryState(ProtoOutputStream proto, long fieldId,
375                 long duration, int count) {
376             final long token = proto.start(fieldId);
377             proto.write(DataSetProto.PackageEntryProto.State.DURATION_MS, duration);
378             proto.write(DataSetProto.PackageEntryProto.State.COUNT, count);
379             proto.end(token);
380         }
381 
dump(ProtoOutputStream proto, long fieldId, long now, long nowElapsed, int filterUid)382         void dump(ProtoOutputStream proto, long fieldId, long now, long nowElapsed, int filterUid) {
383             final long token = proto.start(fieldId);
384             final long period = getTotalTime(now);
385 
386             proto.write(DataSetProto.START_CLOCK_TIME_MS, mStartClockTime);
387             proto.write(DataSetProto.ELAPSED_TIME_MS, nowElapsed - mStartElapsedTime);
388             proto.write(DataSetProto.PERIOD_MS, period);
389 
390             final int NE = mEntries.size();
391             for (int i = 0; i < NE; i++) {
392                 int uid = mEntries.keyAt(i);
393                 if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
394                     continue;
395                 }
396                 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
397                 final int NP = uidMap.size();
398                 for (int j = 0; j < NP; j++) {
399                     final long peToken = proto.start(DataSetProto.PACKAGE_ENTRIES);
400                     PackageEntry pe = uidMap.valueAt(j);
401 
402                     proto.write(DataSetProto.PackageEntryProto.UID, uid);
403                     proto.write(DataSetProto.PackageEntryProto.PACKAGE_NAME, uidMap.keyAt(j));
404 
405                     printPackageEntryState(proto, DataSetProto.PackageEntryProto.PENDING_STATE,
406                             pe.getPendingTime(now), pe.pendingCount);
407                     printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_STATE,
408                             pe.getActiveTime(now), pe.activeCount);
409                     printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_TOP_STATE,
410                             pe.getActiveTopTime(now), pe.activeTopCount);
411 
412                     proto.write(DataSetProto.PackageEntryProto.PENDING,
413                           pe.pendingNesting > 0 || pe.hadPending);
414                     proto.write(DataSetProto.PackageEntryProto.ACTIVE,
415                           pe.activeNesting > 0 || pe.hadActive);
416                     proto.write(DataSetProto.PackageEntryProto.ACTIVE_TOP,
417                           pe.activeTopNesting > 0 || pe.hadActiveTop);
418 
419                     for (int k = 0; k < pe.stopReasons.size(); k++) {
420                         final long srcToken =
421                                 proto.start(DataSetProto.PackageEntryProto.STOP_REASONS);
422 
423                         proto.write(DataSetProto.PackageEntryProto.StopReasonCount.REASON,
424                                 pe.stopReasons.keyAt(k));
425                         proto.write(DataSetProto.PackageEntryProto.StopReasonCount.COUNT,
426                                 pe.stopReasons.valueAt(k));
427 
428                         proto.end(srcToken);
429                     }
430 
431                     proto.end(peToken);
432                 }
433             }
434 
435             proto.write(DataSetProto.MAX_CONCURRENCY, mMaxTotalActive);
436             proto.write(DataSetProto.MAX_FOREGROUND_CONCURRENCY, mMaxFgActive);
437 
438             proto.end(token);
439         }
440     }
441 
rebatchIfNeeded(long now)442     void rebatchIfNeeded(long now) {
443         long totalTime = mCurDataSet.getTotalTime(now);
444         if (totalTime > BATCHING_TIME) {
445             DataSet last = mCurDataSet;
446             last.mSummedTime = totalTime;
447             mCurDataSet = new DataSet();
448             last.finish(mCurDataSet, now);
449             System.arraycopy(mLastDataSets, 0, mLastDataSets, 1, mLastDataSets.length-1);
450             mLastDataSets[0] = last;
451         }
452     }
453 
notePending(JobStatus job)454     public void notePending(JobStatus job) {
455         final long now = sUptimeMillisClock.millis();
456         job.madePending = now;
457         rebatchIfNeeded(now);
458         mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now);
459     }
460 
noteNonpending(JobStatus job)461     public void noteNonpending(JobStatus job) {
462         final long now = sUptimeMillisClock.millis();
463         mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now);
464         rebatchIfNeeded(now);
465     }
466 
noteActive(JobStatus job)467     public void noteActive(JobStatus job) {
468         final long now = sUptimeMillisClock.millis();
469         job.madeActive = now;
470         rebatchIfNeeded(now);
471         if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
472             mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
473         } else {
474             mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now);
475         }
476         addEvent(job.getJob().isPeriodic() ? EVENT_START_PERIODIC_JOB :  EVENT_START_JOB,
477                 job.getSourceUid(), job.getBatteryName(), job.getJobId(), 0, null);
478     }
479 
noteInactive(JobStatus job, int stopReason, String debugReason)480     public void noteInactive(JobStatus job, int stopReason, String debugReason) {
481         final long now = sUptimeMillisClock.millis();
482         if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
483             mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
484                     stopReason);
485         } else {
486             mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now, stopReason);
487         }
488         rebatchIfNeeded(now);
489         addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB :  EVENT_STOP_PERIODIC_JOB,
490                 job.getSourceUid(), job.getBatteryName(), job.getJobId(), stopReason, debugReason);
491     }
492 
noteConcurrency(int totalActive, int fgActive)493     public void noteConcurrency(int totalActive, int fgActive) {
494         if (totalActive > mCurDataSet.mMaxTotalActive) {
495             mCurDataSet.mMaxTotalActive = totalActive;
496         }
497         if (fgActive > mCurDataSet.mMaxFgActive) {
498             mCurDataSet.mMaxFgActive = fgActive;
499         }
500     }
501 
getLoadFactor(JobStatus job)502     public float getLoadFactor(JobStatus job) {
503         final int uid = job.getSourceUid();
504         final String pkg = job.getSourcePackageName();
505         PackageEntry cur = mCurDataSet.getEntry(uid, pkg);
506         PackageEntry last = mLastDataSets[0] != null ? mLastDataSets[0].getEntry(uid, pkg) : null;
507         if (cur == null && last == null) {
508             return 0;
509         }
510         final long now = sUptimeMillisClock.millis();
511         long time = 0;
512         if (cur != null) {
513             time += cur.getActiveTime(now) + cur.getPendingTime(now);
514         }
515         long period = mCurDataSet.getTotalTime(now);
516         if (last != null) {
517             time += last.getActiveTime(now) + last.getPendingTime(now);
518             period += mLastDataSets[0].getTotalTime(now);
519         }
520         return time / (float)period;
521     }
522 
dump(PrintWriter pw, String prefix, int filterUid)523     public void dump(PrintWriter pw, String prefix, int filterUid) {
524         final long now = sUptimeMillisClock.millis();
525         final long nowElapsed = sElapsedRealtimeClock.millis();
526         final DataSet total;
527         if (mLastDataSets[0] != null) {
528             total = new DataSet(mLastDataSets[0]);
529             mLastDataSets[0].addTo(total, now);
530         } else {
531             total = new DataSet(mCurDataSet);
532         }
533         mCurDataSet.addTo(total, now);
534         for (int i = 1; i < mLastDataSets.length; i++) {
535             if (mLastDataSets[i] != null) {
536                 mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowElapsed, filterUid);
537                 pw.println();
538             }
539         }
540         total.dump(pw, "Current stats", prefix, now, nowElapsed, filterUid);
541     }
542 
dump(ProtoOutputStream proto, long fieldId, int filterUid)543     public void dump(ProtoOutputStream proto, long fieldId, int filterUid) {
544         final long token = proto.start(fieldId);
545         final long now = sUptimeMillisClock.millis();
546         final long nowElapsed = sElapsedRealtimeClock.millis();
547 
548         final DataSet total;
549         if (mLastDataSets[0] != null) {
550             total = new DataSet(mLastDataSets[0]);
551             mLastDataSets[0].addTo(total, now);
552         } else {
553             total = new DataSet(mCurDataSet);
554         }
555         mCurDataSet.addTo(total, now);
556 
557         for (int i = 1; i < mLastDataSets.length; i++) {
558             if (mLastDataSets[i] != null) {
559                 mLastDataSets[i].dump(proto, JobPackageTrackerDumpProto.HISTORICAL_STATS,
560                         now, nowElapsed, filterUid);
561             }
562         }
563         total.dump(proto, JobPackageTrackerDumpProto.CURRENT_STATS,
564                 now, nowElapsed, filterUid);
565 
566         proto.end(token);
567     }
568 
dumpHistory(PrintWriter pw, String prefix, int filterUid)569     public boolean dumpHistory(PrintWriter pw, String prefix, int filterUid) {
570         final int size = mEventIndices.size();
571         if (size <= 0) {
572             return false;
573         }
574         pw.println("  Job history:");
575         final long now = sElapsedRealtimeClock.millis();
576         for (int i=0; i<size; i++) {
577             final int index = mEventIndices.indexOf(i);
578             final int uid = mEventUids[index];
579             if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
580                 continue;
581             }
582             final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
583             if (cmd == EVENT_NULL) {
584                 continue;
585             }
586             final String label;
587             switch (cmd) {
588                 case EVENT_START_JOB:           label = "  START"; break;
589                 case EVENT_STOP_JOB:            label = "   STOP"; break;
590                 case EVENT_START_PERIODIC_JOB:  label = "START-P"; break;
591                 case EVENT_STOP_PERIODIC_JOB:   label = " STOP-P"; break;
592                 default:                        label = "     ??"; break;
593             }
594             pw.print(prefix);
595             TimeUtils.formatDuration(mEventTimes[index]-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
596             pw.print(" ");
597             pw.print(label);
598             pw.print(": #");
599             UserHandle.formatUid(pw, uid);
600             pw.print("/");
601             pw.print(mEventJobIds[index]);
602             pw.print(" ");
603             pw.print(mEventTags[index]);
604             if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
605                 pw.print(" ");
606                 final String reason = mEventReasons[index];
607                 if (reason != null) {
608                     pw.print(mEventReasons[index]);
609                 } else {
610                     pw.print(JobParameters.getReasonCodeDescription(
611                             (mEventCmds[index] & EVENT_STOP_REASON_MASK)
612                                     >> EVENT_STOP_REASON_SHIFT));
613                 }
614             }
615             pw.println();
616         }
617         return true;
618     }
619 
dumpHistory(ProtoOutputStream proto, long fieldId, int filterUid)620     public void dumpHistory(ProtoOutputStream proto, long fieldId, int filterUid) {
621         final int size = mEventIndices.size();
622         if (size == 0) {
623             return;
624         }
625         final long token = proto.start(fieldId);
626 
627         final long now = sElapsedRealtimeClock.millis();
628         for (int i = 0; i < size; i++) {
629             final int index = mEventIndices.indexOf(i);
630             final int uid = mEventUids[index];
631             if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
632                 continue;
633             }
634             final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
635             if (cmd == EVENT_NULL) {
636                 continue;
637             }
638             final long heToken = proto.start(JobPackageHistoryProto.HISTORY_EVENT);
639 
640             proto.write(JobPackageHistoryProto.HistoryEvent.EVENT, cmd);
641             proto.write(JobPackageHistoryProto.HistoryEvent.TIME_SINCE_EVENT_MS, now - mEventTimes[index]);
642             proto.write(JobPackageHistoryProto.HistoryEvent.UID, uid);
643             proto.write(JobPackageHistoryProto.HistoryEvent.JOB_ID, mEventJobIds[index]);
644             proto.write(JobPackageHistoryProto.HistoryEvent.TAG, mEventTags[index]);
645             if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
646                 proto.write(JobPackageHistoryProto.HistoryEvent.STOP_REASON,
647                     (mEventCmds[index] & EVENT_STOP_REASON_MASK) >> EVENT_STOP_REASON_SHIFT);
648             }
649 
650             proto.end(heToken);
651         }
652 
653         proto.end(token);
654     }
655 }
656