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