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.getReasonName(pe.stopReasons.keyAt(k)));
363                         }
364                         pw.println();
365                     }
366                 }
367             }
368             pw.print(prefix); pw.print("  Max concurrency: ");
369             pw.print(mMaxTotalActive); pw.print(" total, ");
370             pw.print(mMaxFgActive); pw.println(" foreground");
371         }
372 
printPackageEntryState(ProtoOutputStream proto, long fieldId, long duration, int count)373         private void printPackageEntryState(ProtoOutputStream proto, long fieldId,
374                 long duration, int count) {
375             final long token = proto.start(fieldId);
376             proto.write(DataSetProto.PackageEntryProto.State.DURATION_MS, duration);
377             proto.write(DataSetProto.PackageEntryProto.State.COUNT, count);
378             proto.end(token);
379         }
380 
dump(ProtoOutputStream proto, long fieldId, long now, long nowElapsed, int filterUid)381         void dump(ProtoOutputStream proto, long fieldId, long now, long nowElapsed, int filterUid) {
382             final long token = proto.start(fieldId);
383             final long period = getTotalTime(now);
384 
385             proto.write(DataSetProto.START_CLOCK_TIME_MS, mStartClockTime);
386             proto.write(DataSetProto.ELAPSED_TIME_MS, nowElapsed - mStartElapsedTime);
387             proto.write(DataSetProto.PERIOD_MS, period);
388 
389             final int NE = mEntries.size();
390             for (int i = 0; i < NE; i++) {
391                 int uid = mEntries.keyAt(i);
392                 if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
393                     continue;
394                 }
395                 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
396                 final int NP = uidMap.size();
397                 for (int j = 0; j < NP; j++) {
398                     final long peToken = proto.start(DataSetProto.PACKAGE_ENTRIES);
399                     PackageEntry pe = uidMap.valueAt(j);
400 
401                     proto.write(DataSetProto.PackageEntryProto.UID, uid);
402                     proto.write(DataSetProto.PackageEntryProto.PACKAGE_NAME, uidMap.keyAt(j));
403 
404                     printPackageEntryState(proto, DataSetProto.PackageEntryProto.PENDING_STATE,
405                             pe.getPendingTime(now), pe.pendingCount);
406                     printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_STATE,
407                             pe.getActiveTime(now), pe.activeCount);
408                     printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_TOP_STATE,
409                             pe.getActiveTopTime(now), pe.activeTopCount);
410 
411                     proto.write(DataSetProto.PackageEntryProto.PENDING,
412                           pe.pendingNesting > 0 || pe.hadPending);
413                     proto.write(DataSetProto.PackageEntryProto.ACTIVE,
414                           pe.activeNesting > 0 || pe.hadActive);
415                     proto.write(DataSetProto.PackageEntryProto.ACTIVE_TOP,
416                           pe.activeTopNesting > 0 || pe.hadActiveTop);
417 
418                     for (int k = 0; k < pe.stopReasons.size(); k++) {
419                         final long srcToken =
420                                 proto.start(DataSetProto.PackageEntryProto.STOP_REASONS);
421 
422                         proto.write(DataSetProto.PackageEntryProto.StopReasonCount.REASON,
423                                 pe.stopReasons.keyAt(k));
424                         proto.write(DataSetProto.PackageEntryProto.StopReasonCount.COUNT,
425                                 pe.stopReasons.valueAt(k));
426 
427                         proto.end(srcToken);
428                     }
429 
430                     proto.end(peToken);
431                 }
432             }
433 
434             proto.write(DataSetProto.MAX_CONCURRENCY, mMaxTotalActive);
435             proto.write(DataSetProto.MAX_FOREGROUND_CONCURRENCY, mMaxFgActive);
436 
437             proto.end(token);
438         }
439     }
440 
rebatchIfNeeded(long now)441     void rebatchIfNeeded(long now) {
442         long totalTime = mCurDataSet.getTotalTime(now);
443         if (totalTime > BATCHING_TIME) {
444             DataSet last = mCurDataSet;
445             last.mSummedTime = totalTime;
446             mCurDataSet = new DataSet();
447             last.finish(mCurDataSet, now);
448             System.arraycopy(mLastDataSets, 0, mLastDataSets, 1, mLastDataSets.length-1);
449             mLastDataSets[0] = last;
450         }
451     }
452 
notePending(JobStatus job)453     public void notePending(JobStatus job) {
454         final long now = sUptimeMillisClock.millis();
455         job.madePending = now;
456         rebatchIfNeeded(now);
457         mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now);
458     }
459 
noteNonpending(JobStatus job)460     public void noteNonpending(JobStatus job) {
461         final long now = sUptimeMillisClock.millis();
462         mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now);
463         rebatchIfNeeded(now);
464     }
465 
noteActive(JobStatus job)466     public void noteActive(JobStatus job) {
467         final long now = sUptimeMillisClock.millis();
468         job.madeActive = now;
469         rebatchIfNeeded(now);
470         if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
471             mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
472         } else {
473             mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now);
474         }
475         addEvent(job.getJob().isPeriodic() ? EVENT_START_PERIODIC_JOB :  EVENT_START_JOB,
476                 job.getSourceUid(), job.getBatteryName(), job.getJobId(), 0, null);
477     }
478 
noteInactive(JobStatus job, int stopReason, String debugReason)479     public void noteInactive(JobStatus job, int stopReason, String debugReason) {
480         final long now = sUptimeMillisClock.millis();
481         if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
482             mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
483                     stopReason);
484         } else {
485             mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now, stopReason);
486         }
487         rebatchIfNeeded(now);
488         addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB :  EVENT_STOP_PERIODIC_JOB,
489                 job.getSourceUid(), job.getBatteryName(), job.getJobId(), stopReason, debugReason);
490     }
491 
noteConcurrency(int totalActive, int fgActive)492     public void noteConcurrency(int totalActive, int fgActive) {
493         if (totalActive > mCurDataSet.mMaxTotalActive) {
494             mCurDataSet.mMaxTotalActive = totalActive;
495         }
496         if (fgActive > mCurDataSet.mMaxFgActive) {
497             mCurDataSet.mMaxFgActive = fgActive;
498         }
499     }
500 
getLoadFactor(JobStatus job)501     public float getLoadFactor(JobStatus job) {
502         final int uid = job.getSourceUid();
503         final String pkg = job.getSourcePackageName();
504         PackageEntry cur = mCurDataSet.getEntry(uid, pkg);
505         PackageEntry last = mLastDataSets[0] != null ? mLastDataSets[0].getEntry(uid, pkg) : null;
506         if (cur == null && last == null) {
507             return 0;
508         }
509         final long now = sUptimeMillisClock.millis();
510         long time = 0;
511         if (cur != null) {
512             time += cur.getActiveTime(now) + cur.getPendingTime(now);
513         }
514         long period = mCurDataSet.getTotalTime(now);
515         if (last != null) {
516             time += last.getActiveTime(now) + last.getPendingTime(now);
517             period += mLastDataSets[0].getTotalTime(now);
518         }
519         return time / (float)period;
520     }
521 
dump(PrintWriter pw, String prefix, int filterUid)522     public void dump(PrintWriter pw, String prefix, int filterUid) {
523         final long now = sUptimeMillisClock.millis();
524         final long nowElapsed = sElapsedRealtimeClock.millis();
525         final DataSet total;
526         if (mLastDataSets[0] != null) {
527             total = new DataSet(mLastDataSets[0]);
528             mLastDataSets[0].addTo(total, now);
529         } else {
530             total = new DataSet(mCurDataSet);
531         }
532         mCurDataSet.addTo(total, now);
533         for (int i = 1; i < mLastDataSets.length; i++) {
534             if (mLastDataSets[i] != null) {
535                 mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowElapsed, filterUid);
536                 pw.println();
537             }
538         }
539         total.dump(pw, "Current stats", prefix, now, nowElapsed, filterUid);
540     }
541 
dump(ProtoOutputStream proto, long fieldId, int filterUid)542     public void dump(ProtoOutputStream proto, long fieldId, int filterUid) {
543         final long token = proto.start(fieldId);
544         final long now = sUptimeMillisClock.millis();
545         final long nowElapsed = sElapsedRealtimeClock.millis();
546 
547         final DataSet total;
548         if (mLastDataSets[0] != null) {
549             total = new DataSet(mLastDataSets[0]);
550             mLastDataSets[0].addTo(total, now);
551         } else {
552             total = new DataSet(mCurDataSet);
553         }
554         mCurDataSet.addTo(total, now);
555 
556         for (int i = 1; i < mLastDataSets.length; i++) {
557             if (mLastDataSets[i] != null) {
558                 mLastDataSets[i].dump(proto, JobPackageTrackerDumpProto.HISTORICAL_STATS,
559                         now, nowElapsed, filterUid);
560             }
561         }
562         total.dump(proto, JobPackageTrackerDumpProto.CURRENT_STATS,
563                 now, nowElapsed, filterUid);
564 
565         proto.end(token);
566     }
567 
dumpHistory(PrintWriter pw, String prefix, int filterUid)568     public boolean dumpHistory(PrintWriter pw, String prefix, int filterUid) {
569         final int size = mEventIndices.size();
570         if (size <= 0) {
571             return false;
572         }
573         pw.println("  Job history:");
574         final long now = sElapsedRealtimeClock.millis();
575         for (int i=0; i<size; i++) {
576             final int index = mEventIndices.indexOf(i);
577             final int uid = mEventUids[index];
578             if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
579                 continue;
580             }
581             final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
582             if (cmd == EVENT_NULL) {
583                 continue;
584             }
585             final String label;
586             switch (cmd) {
587                 case EVENT_START_JOB:           label = "  START"; break;
588                 case EVENT_STOP_JOB:            label = "   STOP"; break;
589                 case EVENT_START_PERIODIC_JOB:  label = "START-P"; break;
590                 case EVENT_STOP_PERIODIC_JOB:   label = " STOP-P"; break;
591                 default:                        label = "     ??"; break;
592             }
593             pw.print(prefix);
594             TimeUtils.formatDuration(mEventTimes[index]-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
595             pw.print(" ");
596             pw.print(label);
597             pw.print(": #");
598             UserHandle.formatUid(pw, uid);
599             pw.print("/");
600             pw.print(mEventJobIds[index]);
601             pw.print(" ");
602             pw.print(mEventTags[index]);
603             if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
604                 pw.print(" ");
605                 final String reason = mEventReasons[index];
606                 if (reason != null) {
607                     pw.print(mEventReasons[index]);
608                 } else {
609                     pw.print(JobParameters.getReasonName((mEventCmds[index] & EVENT_STOP_REASON_MASK)
610                             >> EVENT_STOP_REASON_SHIFT));
611                 }
612             }
613             pw.println();
614         }
615         return true;
616     }
617 
dumpHistory(ProtoOutputStream proto, long fieldId, int filterUid)618     public void dumpHistory(ProtoOutputStream proto, long fieldId, int filterUid) {
619         final int size = mEventIndices.size();
620         if (size == 0) {
621             return;
622         }
623         final long token = proto.start(fieldId);
624 
625         final long now = sElapsedRealtimeClock.millis();
626         for (int i = 0; i < size; i++) {
627             final int index = mEventIndices.indexOf(i);
628             final int uid = mEventUids[index];
629             if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
630                 continue;
631             }
632             final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
633             if (cmd == EVENT_NULL) {
634                 continue;
635             }
636             final long heToken = proto.start(JobPackageHistoryProto.HISTORY_EVENT);
637 
638             proto.write(JobPackageHistoryProto.HistoryEvent.EVENT, cmd);
639             proto.write(JobPackageHistoryProto.HistoryEvent.TIME_SINCE_EVENT_MS, now - mEventTimes[index]);
640             proto.write(JobPackageHistoryProto.HistoryEvent.UID, uid);
641             proto.write(JobPackageHistoryProto.HistoryEvent.JOB_ID, mEventJobIds[index]);
642             proto.write(JobPackageHistoryProto.HistoryEvent.TAG, mEventTags[index]);
643             if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
644                 proto.write(JobPackageHistoryProto.HistoryEvent.STOP_REASON,
645                     (mEventCmds[index] & EVENT_STOP_REASON_MASK) >> EVENT_STOP_REASON_SHIFT);
646             }
647 
648             proto.end(heToken);
649         }
650 
651         proto.end(token);
652     }
653 }
654