Home | History | Annotate | Download | only in job
      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 
     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