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
17package com.android.server.job;
18
19import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
20import static com.android.server.job.JobSchedulerService.sSystemClock;
21import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
22
23import android.app.job.JobInfo;
24import android.app.job.JobParameters;
25import android.os.UserHandle;
26import android.text.format.DateFormat;
27import android.util.ArrayMap;
28import android.util.SparseArray;
29import android.util.SparseIntArray;
30import android.util.TimeUtils;
31import android.util.proto.ProtoOutputStream;
32
33import com.android.internal.util.RingBufferIndices;
34import com.android.server.job.controllers.JobStatus;
35
36import java.io.PrintWriter;
37
38public 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
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
95        public long getActiveTime(long now) {
96            long time = pastActiveTime;
97            if (activeNesting > 0) {
98                time += now - activeStartTime;
99            }
100            return time;
101        }
102
103        public long getActiveTopTime(long now) {
104            long time = pastActiveTopTime;
105            if (activeTopNesting > 0) {
106                time += now - activeTopStartTime;
107            }
108            return time;
109        }
110
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
129        public DataSet(DataSet otherTimes) {
130            mStartUptimeTime = otherTimes.mStartUptimeTime;
131            mStartElapsedTime = otherTimes.mStartElapsedTime;
132            mStartClockTime = otherTimes.mStartClockTime;
133        }
134
135        public DataSet() {
136            mStartUptimeTime = sUptimeMillisClock.millis();
137            mStartElapsedTime = sElapsedRealtimeClock.millis();
138            mStartClockTime = sSystemClock.millis();
139        }
140
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
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
163        long getTotalTime(long now) {
164            if (mSummedTime > 0) {
165                return mSummedTime;
166            }
167            return now - mStartUptimeTime;
168        }
169
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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