| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.server.job; |
| |
| import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; |
| import static com.android.server.job.JobSchedulerService.sSystemClock; |
| import static com.android.server.job.JobSchedulerService.sUptimeMillisClock; |
| |
| import android.app.job.JobInfo; |
| import android.app.job.JobParameters; |
| import android.os.UserHandle; |
| import android.text.format.DateFormat; |
| import android.util.ArrayMap; |
| import android.util.SparseArray; |
| import android.util.SparseIntArray; |
| import android.util.TimeUtils; |
| import android.util.proto.ProtoOutputStream; |
| |
| import com.android.internal.util.RingBufferIndices; |
| import com.android.server.job.controllers.JobStatus; |
| |
| import java.io.PrintWriter; |
| |
| public final class JobPackageTracker { |
| // We batch every 30 minutes. |
| static final long BATCHING_TIME = 30*60*1000; |
| // Number of historical data sets we keep. |
| static final int NUM_HISTORY = 5; |
| |
| private static final int EVENT_BUFFER_SIZE = 100; |
| |
| public static final int EVENT_CMD_MASK = 0xff; |
| public static final int EVENT_STOP_REASON_SHIFT = 8; |
| public static final int EVENT_STOP_REASON_MASK = 0xff << EVENT_STOP_REASON_SHIFT; |
| public static final int EVENT_NULL = 0; |
| public static final int EVENT_START_JOB = 1; |
| public static final int EVENT_STOP_JOB = 2; |
| public static final int EVENT_START_PERIODIC_JOB = 3; |
| public static final int EVENT_STOP_PERIODIC_JOB = 4; |
| |
| private final RingBufferIndices mEventIndices = new RingBufferIndices(EVENT_BUFFER_SIZE); |
| private final int[] mEventCmds = new int[EVENT_BUFFER_SIZE]; |
| private final long[] mEventTimes = new long[EVENT_BUFFER_SIZE]; |
| private final int[] mEventUids = new int[EVENT_BUFFER_SIZE]; |
| private final String[] mEventTags = new String[EVENT_BUFFER_SIZE]; |
| private final int[] mEventJobIds = new int[EVENT_BUFFER_SIZE]; |
| private final String[] mEventReasons = new String[EVENT_BUFFER_SIZE]; |
| |
| public void addEvent(int cmd, int uid, String tag, int jobId, int stopReason, |
| String debugReason) { |
| int index = mEventIndices.add(); |
| mEventCmds[index] = cmd | ((stopReason<<EVENT_STOP_REASON_SHIFT) & EVENT_STOP_REASON_MASK); |
| mEventTimes[index] = sElapsedRealtimeClock.millis(); |
| mEventUids[index] = uid; |
| mEventTags[index] = tag; |
| mEventJobIds[index] = jobId; |
| mEventReasons[index] = debugReason; |
| } |
| |
| DataSet mCurDataSet = new DataSet(); |
| DataSet[] mLastDataSets = new DataSet[NUM_HISTORY]; |
| |
| final static class PackageEntry { |
| long pastActiveTime; |
| long activeStartTime; |
| int activeNesting; |
| int activeCount; |
| boolean hadActive; |
| long pastActiveTopTime; |
| long activeTopStartTime; |
| int activeTopNesting; |
| int activeTopCount; |
| boolean hadActiveTop; |
| long pastPendingTime; |
| long pendingStartTime; |
| int pendingNesting; |
| int pendingCount; |
| boolean hadPending; |
| final SparseIntArray stopReasons = new SparseIntArray(); |
| |
| public long getActiveTime(long now) { |
| long time = pastActiveTime; |
| if (activeNesting > 0) { |
| time += now - activeStartTime; |
| } |
| return time; |
| } |
| |
| public long getActiveTopTime(long now) { |
| long time = pastActiveTopTime; |
| if (activeTopNesting > 0) { |
| time += now - activeTopStartTime; |
| } |
| return time; |
| } |
| |
| public long getPendingTime(long now) { |
| long time = pastPendingTime; |
| if (pendingNesting > 0) { |
| time += now - pendingStartTime; |
| } |
| return time; |
| } |
| } |
| |
| final static class DataSet { |
| final SparseArray<ArrayMap<String, PackageEntry>> mEntries = new SparseArray<>(); |
| final long mStartUptimeTime; |
| final long mStartElapsedTime; |
| final long mStartClockTime; |
| long mSummedTime; |
| int mMaxTotalActive; |
| int mMaxFgActive; |
| |
| public DataSet(DataSet otherTimes) { |
| mStartUptimeTime = otherTimes.mStartUptimeTime; |
| mStartElapsedTime = otherTimes.mStartElapsedTime; |
| mStartClockTime = otherTimes.mStartClockTime; |
| } |
| |
| public DataSet() { |
| mStartUptimeTime = sUptimeMillisClock.millis(); |
| mStartElapsedTime = sElapsedRealtimeClock.millis(); |
| mStartClockTime = sSystemClock.millis(); |
| } |
| |
| private PackageEntry getOrCreateEntry(int uid, String pkg) { |
| ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid); |
| if (uidMap == null) { |
| uidMap = new ArrayMap<>(); |
| mEntries.put(uid, uidMap); |
| } |
| PackageEntry entry = uidMap.get(pkg); |
| if (entry == null) { |
| entry = new PackageEntry(); |
| uidMap.put(pkg, entry); |
| } |
| return entry; |
| } |
| |
| public PackageEntry getEntry(int uid, String pkg) { |
| ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid); |
| if (uidMap == null) { |
| return null; |
| } |
| return uidMap.get(pkg); |
| } |
| |
| long getTotalTime(long now) { |
| if (mSummedTime > 0) { |
| return mSummedTime; |
| } |
| return now - mStartUptimeTime; |
| } |
| |
| void incPending(int uid, String pkg, long now) { |
| PackageEntry pe = getOrCreateEntry(uid, pkg); |
| if (pe.pendingNesting == 0) { |
| pe.pendingStartTime = now; |
| pe.pendingCount++; |
| } |
| pe.pendingNesting++; |
| } |
| |
| void decPending(int uid, String pkg, long now) { |
| PackageEntry pe = getOrCreateEntry(uid, pkg); |
| if (pe.pendingNesting == 1) { |
| pe.pastPendingTime += now - pe.pendingStartTime; |
| } |
| pe.pendingNesting--; |
| } |
| |
| void incActive(int uid, String pkg, long now) { |
| PackageEntry pe = getOrCreateEntry(uid, pkg); |
| if (pe.activeNesting == 0) { |
| pe.activeStartTime = now; |
| pe.activeCount++; |
| } |
| pe.activeNesting++; |
| } |
| |
| void decActive(int uid, String pkg, long now, int stopReason) { |
| PackageEntry pe = getOrCreateEntry(uid, pkg); |
| if (pe.activeNesting == 1) { |
| pe.pastActiveTime += now - pe.activeStartTime; |
| } |
| pe.activeNesting--; |
| int count = pe.stopReasons.get(stopReason, 0); |
| pe.stopReasons.put(stopReason, count+1); |
| } |
| |
| void incActiveTop(int uid, String pkg, long now) { |
| PackageEntry pe = getOrCreateEntry(uid, pkg); |
| if (pe.activeTopNesting == 0) { |
| pe.activeTopStartTime = now; |
| pe.activeTopCount++; |
| } |
| pe.activeTopNesting++; |
| } |
| |
| void decActiveTop(int uid, String pkg, long now, int stopReason) { |
| PackageEntry pe = getOrCreateEntry(uid, pkg); |
| if (pe.activeTopNesting == 1) { |
| pe.pastActiveTopTime += now - pe.activeTopStartTime; |
| } |
| pe.activeTopNesting--; |
| int count = pe.stopReasons.get(stopReason, 0); |
| pe.stopReasons.put(stopReason, count+1); |
| } |
| |
| void finish(DataSet next, long now) { |
| for (int i = mEntries.size() - 1; i >= 0; i--) { |
| ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); |
| for (int j = uidMap.size() - 1; j >= 0; j--) { |
| PackageEntry pe = uidMap.valueAt(j); |
| if (pe.activeNesting > 0 || pe.activeTopNesting > 0 || pe.pendingNesting > 0) { |
| // Propagate existing activity in to next data set. |
| PackageEntry nextPe = next.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j)); |
| nextPe.activeStartTime = now; |
| nextPe.activeNesting = pe.activeNesting; |
| nextPe.activeTopStartTime = now; |
| nextPe.activeTopNesting = pe.activeTopNesting; |
| nextPe.pendingStartTime = now; |
| nextPe.pendingNesting = pe.pendingNesting; |
| // Finish it off. |
| if (pe.activeNesting > 0) { |
| pe.pastActiveTime += now - pe.activeStartTime; |
| pe.activeNesting = 0; |
| } |
| if (pe.activeTopNesting > 0) { |
| pe.pastActiveTopTime += now - pe.activeTopStartTime; |
| pe.activeTopNesting = 0; |
| } |
| if (pe.pendingNesting > 0) { |
| pe.pastPendingTime += now - pe.pendingStartTime; |
| pe.pendingNesting = 0; |
| } |
| } |
| } |
| } |
| } |
| |
| void addTo(DataSet out, long now) { |
| out.mSummedTime += getTotalTime(now); |
| for (int i = mEntries.size() - 1; i >= 0; i--) { |
| ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); |
| for (int j = uidMap.size() - 1; j >= 0; j--) { |
| PackageEntry pe = uidMap.valueAt(j); |
| PackageEntry outPe = out.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j)); |
| outPe.pastActiveTime += pe.pastActiveTime; |
| outPe.activeCount += pe.activeCount; |
| outPe.pastActiveTopTime += pe.pastActiveTopTime; |
| outPe.activeTopCount += pe.activeTopCount; |
| outPe.pastPendingTime += pe.pastPendingTime; |
| outPe.pendingCount += pe.pendingCount; |
| if (pe.activeNesting > 0) { |
| outPe.pastActiveTime += now - pe.activeStartTime; |
| outPe.hadActive = true; |
| } |
| if (pe.activeTopNesting > 0) { |
| outPe.pastActiveTopTime += now - pe.activeTopStartTime; |
| outPe.hadActiveTop = true; |
| } |
| if (pe.pendingNesting > 0) { |
| outPe.pastPendingTime += now - pe.pendingStartTime; |
| outPe.hadPending = true; |
| } |
| for (int k = pe.stopReasons.size()-1; k >= 0; k--) { |
| int type = pe.stopReasons.keyAt(k); |
| outPe.stopReasons.put(type, outPe.stopReasons.get(type, 0) |
| + pe.stopReasons.valueAt(k)); |
| } |
| } |
| } |
| if (mMaxTotalActive > out.mMaxTotalActive) { |
| out.mMaxTotalActive = mMaxTotalActive; |
| } |
| if (mMaxFgActive > out.mMaxFgActive) { |
| out.mMaxFgActive = mMaxFgActive; |
| } |
| } |
| |
| void printDuration(PrintWriter pw, long period, long duration, int count, String suffix) { |
| float fraction = duration / (float) period; |
| int percent = (int) ((fraction * 100) + .5f); |
| if (percent > 0) { |
| pw.print(" "); |
| pw.print(percent); |
| pw.print("% "); |
| pw.print(count); |
| pw.print("x "); |
| pw.print(suffix); |
| } else if (count > 0) { |
| pw.print(" "); |
| pw.print(count); |
| pw.print("x "); |
| pw.print(suffix); |
| } |
| } |
| |
| void dump(PrintWriter pw, String header, String prefix, long now, long nowElapsed, |
| int filterUid) { |
| final long period = getTotalTime(now); |
| pw.print(prefix); pw.print(header); pw.print(" at "); |
| pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString()); |
| pw.print(" ("); |
| TimeUtils.formatDuration(mStartElapsedTime, nowElapsed, pw); |
| pw.print(") over "); |
| TimeUtils.formatDuration(period, pw); |
| pw.println(":"); |
| final int NE = mEntries.size(); |
| for (int i = 0; i < NE; i++) { |
| int uid = mEntries.keyAt(i); |
| if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) { |
| continue; |
| } |
| ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); |
| final int NP = uidMap.size(); |
| for (int j = 0; j < NP; j++) { |
| PackageEntry pe = uidMap.valueAt(j); |
| pw.print(prefix); pw.print(" "); |
| UserHandle.formatUid(pw, uid); |
| pw.print(" / "); pw.print(uidMap.keyAt(j)); |
| pw.println(":"); |
| pw.print(prefix); pw.print(" "); |
| printDuration(pw, period, pe.getPendingTime(now), pe.pendingCount, "pending"); |
| printDuration(pw, period, pe.getActiveTime(now), pe.activeCount, "active"); |
| printDuration(pw, period, pe.getActiveTopTime(now), pe.activeTopCount, |
| "active-top"); |
| if (pe.pendingNesting > 0 || pe.hadPending) { |
| pw.print(" (pending)"); |
| } |
| if (pe.activeNesting > 0 || pe.hadActive) { |
| pw.print(" (active)"); |
| } |
| if (pe.activeTopNesting > 0 || pe.hadActiveTop) { |
| pw.print(" (active-top)"); |
| } |
| pw.println(); |
| if (pe.stopReasons.size() > 0) { |
| pw.print(prefix); pw.print(" "); |
| for (int k = 0; k < pe.stopReasons.size(); k++) { |
| if (k > 0) { |
| pw.print(", "); |
| } |
| pw.print(pe.stopReasons.valueAt(k)); |
| pw.print("x "); |
| pw.print(JobParameters.getReasonName(pe.stopReasons.keyAt(k))); |
| } |
| pw.println(); |
| } |
| } |
| } |
| pw.print(prefix); pw.print(" Max concurrency: "); |
| pw.print(mMaxTotalActive); pw.print(" total, "); |
| pw.print(mMaxFgActive); pw.println(" foreground"); |
| } |
| |
| private void printPackageEntryState(ProtoOutputStream proto, long fieldId, |
| long duration, int count) { |
| final long token = proto.start(fieldId); |
| proto.write(DataSetProto.PackageEntryProto.State.DURATION_MS, duration); |
| proto.write(DataSetProto.PackageEntryProto.State.COUNT, count); |
| proto.end(token); |
| } |
| |
| void dump(ProtoOutputStream proto, long fieldId, long now, long nowElapsed, int filterUid) { |
| final long token = proto.start(fieldId); |
| final long period = getTotalTime(now); |
| |
| proto.write(DataSetProto.START_CLOCK_TIME_MS, mStartClockTime); |
| proto.write(DataSetProto.ELAPSED_TIME_MS, nowElapsed - mStartElapsedTime); |
| proto.write(DataSetProto.PERIOD_MS, period); |
| |
| final int NE = mEntries.size(); |
| for (int i = 0; i < NE; i++) { |
| int uid = mEntries.keyAt(i); |
| if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) { |
| continue; |
| } |
| ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); |
| final int NP = uidMap.size(); |
| for (int j = 0; j < NP; j++) { |
| final long peToken = proto.start(DataSetProto.PACKAGE_ENTRIES); |
| PackageEntry pe = uidMap.valueAt(j); |
| |
| proto.write(DataSetProto.PackageEntryProto.UID, uid); |
| proto.write(DataSetProto.PackageEntryProto.PACKAGE_NAME, uidMap.keyAt(j)); |
| |
| printPackageEntryState(proto, DataSetProto.PackageEntryProto.PENDING_STATE, |
| pe.getPendingTime(now), pe.pendingCount); |
| printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_STATE, |
| pe.getActiveTime(now), pe.activeCount); |
| printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_TOP_STATE, |
| pe.getActiveTopTime(now), pe.activeTopCount); |
| |
| proto.write(DataSetProto.PackageEntryProto.PENDING, |
| pe.pendingNesting > 0 || pe.hadPending); |
| proto.write(DataSetProto.PackageEntryProto.ACTIVE, |
| pe.activeNesting > 0 || pe.hadActive); |
| proto.write(DataSetProto.PackageEntryProto.ACTIVE_TOP, |
| pe.activeTopNesting > 0 || pe.hadActiveTop); |
| |
| for (int k = 0; k < pe.stopReasons.size(); k++) { |
| final long srcToken = |
| proto.start(DataSetProto.PackageEntryProto.STOP_REASONS); |
| |
| proto.write(DataSetProto.PackageEntryProto.StopReasonCount.REASON, |
| pe.stopReasons.keyAt(k)); |
| proto.write(DataSetProto.PackageEntryProto.StopReasonCount.COUNT, |
| pe.stopReasons.valueAt(k)); |
| |
| proto.end(srcToken); |
| } |
| |
| proto.end(peToken); |
| } |
| } |
| |
| proto.write(DataSetProto.MAX_CONCURRENCY, mMaxTotalActive); |
| proto.write(DataSetProto.MAX_FOREGROUND_CONCURRENCY, mMaxFgActive); |
| |
| proto.end(token); |
| } |
| } |
| |
| void rebatchIfNeeded(long now) { |
| long totalTime = mCurDataSet.getTotalTime(now); |
| if (totalTime > BATCHING_TIME) { |
| DataSet last = mCurDataSet; |
| last.mSummedTime = totalTime; |
| mCurDataSet = new DataSet(); |
| last.finish(mCurDataSet, now); |
| System.arraycopy(mLastDataSets, 0, mLastDataSets, 1, mLastDataSets.length-1); |
| mLastDataSets[0] = last; |
| } |
| } |
| |
| public void notePending(JobStatus job) { |
| final long now = sUptimeMillisClock.millis(); |
| job.madePending = now; |
| rebatchIfNeeded(now); |
| mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now); |
| } |
| |
| public void noteNonpending(JobStatus job) { |
| final long now = sUptimeMillisClock.millis(); |
| mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now); |
| rebatchIfNeeded(now); |
| } |
| |
| public void noteActive(JobStatus job) { |
| final long now = sUptimeMillisClock.millis(); |
| job.madeActive = now; |
| rebatchIfNeeded(now); |
| if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { |
| mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now); |
| } else { |
| mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now); |
| } |
| addEvent(job.getJob().isPeriodic() ? EVENT_START_PERIODIC_JOB : EVENT_START_JOB, |
| job.getSourceUid(), job.getBatteryName(), job.getJobId(), 0, null); |
| } |
| |
| public void noteInactive(JobStatus job, int stopReason, String debugReason) { |
| final long now = sUptimeMillisClock.millis(); |
| if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { |
| mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now, |
| stopReason); |
| } else { |
| mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now, stopReason); |
| } |
| rebatchIfNeeded(now); |
| addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB : EVENT_STOP_PERIODIC_JOB, |
| job.getSourceUid(), job.getBatteryName(), job.getJobId(), stopReason, debugReason); |
| } |
| |
| public void noteConcurrency(int totalActive, int fgActive) { |
| if (totalActive > mCurDataSet.mMaxTotalActive) { |
| mCurDataSet.mMaxTotalActive = totalActive; |
| } |
| if (fgActive > mCurDataSet.mMaxFgActive) { |
| mCurDataSet.mMaxFgActive = fgActive; |
| } |
| } |
| |
| public float getLoadFactor(JobStatus job) { |
| final int uid = job.getSourceUid(); |
| final String pkg = job.getSourcePackageName(); |
| PackageEntry cur = mCurDataSet.getEntry(uid, pkg); |
| PackageEntry last = mLastDataSets[0] != null ? mLastDataSets[0].getEntry(uid, pkg) : null; |
| if (cur == null && last == null) { |
| return 0; |
| } |
| final long now = sUptimeMillisClock.millis(); |
| long time = 0; |
| if (cur != null) { |
| time += cur.getActiveTime(now) + cur.getPendingTime(now); |
| } |
| long period = mCurDataSet.getTotalTime(now); |
| if (last != null) { |
| time += last.getActiveTime(now) + last.getPendingTime(now); |
| period += mLastDataSets[0].getTotalTime(now); |
| } |
| return time / (float)period; |
| } |
| |
| public void dump(PrintWriter pw, String prefix, int filterUid) { |
| final long now = sUptimeMillisClock.millis(); |
| final long nowElapsed = sElapsedRealtimeClock.millis(); |
| final DataSet total; |
| if (mLastDataSets[0] != null) { |
| total = new DataSet(mLastDataSets[0]); |
| mLastDataSets[0].addTo(total, now); |
| } else { |
| total = new DataSet(mCurDataSet); |
| } |
| mCurDataSet.addTo(total, now); |
| for (int i = 1; i < mLastDataSets.length; i++) { |
| if (mLastDataSets[i] != null) { |
| mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowElapsed, filterUid); |
| pw.println(); |
| } |
| } |
| total.dump(pw, "Current stats", prefix, now, nowElapsed, filterUid); |
| } |
| |
| public void dump(ProtoOutputStream proto, long fieldId, int filterUid) { |
| final long token = proto.start(fieldId); |
| final long now = sUptimeMillisClock.millis(); |
| final long nowElapsed = sElapsedRealtimeClock.millis(); |
| |
| final DataSet total; |
| if (mLastDataSets[0] != null) { |
| total = new DataSet(mLastDataSets[0]); |
| mLastDataSets[0].addTo(total, now); |
| } else { |
| total = new DataSet(mCurDataSet); |
| } |
| mCurDataSet.addTo(total, now); |
| |
| for (int i = 1; i < mLastDataSets.length; i++) { |
| if (mLastDataSets[i] != null) { |
| mLastDataSets[i].dump(proto, JobPackageTrackerDumpProto.HISTORICAL_STATS, |
| now, nowElapsed, filterUid); |
| } |
| } |
| total.dump(proto, JobPackageTrackerDumpProto.CURRENT_STATS, |
| now, nowElapsed, filterUid); |
| |
| proto.end(token); |
| } |
| |
| public boolean dumpHistory(PrintWriter pw, String prefix, int filterUid) { |
| final int size = mEventIndices.size(); |
| if (size <= 0) { |
| return false; |
| } |
| pw.println(" Job history:"); |
| final long now = sElapsedRealtimeClock.millis(); |
| for (int i=0; i<size; i++) { |
| final int index = mEventIndices.indexOf(i); |
| final int uid = mEventUids[index]; |
| if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) { |
| continue; |
| } |
| final int cmd = mEventCmds[index] & EVENT_CMD_MASK; |
| if (cmd == EVENT_NULL) { |
| continue; |
| } |
| final String label; |
| switch (cmd) { |
| case EVENT_START_JOB: label = " START"; break; |
| case EVENT_STOP_JOB: label = " STOP"; break; |
| case EVENT_START_PERIODIC_JOB: label = "START-P"; break; |
| case EVENT_STOP_PERIODIC_JOB: label = " STOP-P"; break; |
| default: label = " ??"; break; |
| } |
| pw.print(prefix); |
| TimeUtils.formatDuration(mEventTimes[index]-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); |
| pw.print(" "); |
| pw.print(label); |
| pw.print(": #"); |
| UserHandle.formatUid(pw, uid); |
| pw.print("/"); |
| pw.print(mEventJobIds[index]); |
| pw.print(" "); |
| pw.print(mEventTags[index]); |
| if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) { |
| pw.print(" "); |
| final String reason = mEventReasons[index]; |
| if (reason != null) { |
| pw.print(mEventReasons[index]); |
| } else { |
| pw.print(JobParameters.getReasonName((mEventCmds[index] & EVENT_STOP_REASON_MASK) |
| >> EVENT_STOP_REASON_SHIFT)); |
| } |
| } |
| pw.println(); |
| } |
| return true; |
| } |
| |
| public void dumpHistory(ProtoOutputStream proto, long fieldId, int filterUid) { |
| final int size = mEventIndices.size(); |
| if (size == 0) { |
| return; |
| } |
| final long token = proto.start(fieldId); |
| |
| final long now = sElapsedRealtimeClock.millis(); |
| for (int i = 0; i < size; i++) { |
| final int index = mEventIndices.indexOf(i); |
| final int uid = mEventUids[index]; |
| if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) { |
| continue; |
| } |
| final int cmd = mEventCmds[index] & EVENT_CMD_MASK; |
| if (cmd == EVENT_NULL) { |
| continue; |
| } |
| final long heToken = proto.start(JobPackageHistoryProto.HISTORY_EVENT); |
| |
| proto.write(JobPackageHistoryProto.HistoryEvent.EVENT, cmd); |
| proto.write(JobPackageHistoryProto.HistoryEvent.TIME_SINCE_EVENT_MS, now - mEventTimes[index]); |
| proto.write(JobPackageHistoryProto.HistoryEvent.UID, uid); |
| proto.write(JobPackageHistoryProto.HistoryEvent.JOB_ID, mEventJobIds[index]); |
| proto.write(JobPackageHistoryProto.HistoryEvent.TAG, mEventTags[index]); |
| if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) { |
| proto.write(JobPackageHistoryProto.HistoryEvent.STOP_REASON, |
| (mEventCmds[index] & EVENT_STOP_REASON_MASK) >> EVENT_STOP_REASON_SHIFT); |
| } |
| |
| proto.end(heToken); |
| } |
| |
| proto.end(token); |
| } |
| } |