private static final String SYNC_DATA = "sy";
private static final String JOB_DATA = "jb";
private static final String JOB_COMPLETION_DATA = "jbc";
+
+ /**
+ * jbd line is:
+ * BATTERY_STATS_CHECKIN_VERSION, uid, which, "jbd",
+ * jobsDeferredEventCount, jobsDeferredCount, averageLatencyMillis,
+ * count at latency < 1 hr, count at latency < 2 hrs, count at latency < 6 hrs, beyond 6 hrs
+ * <p>
+ * @see #JOB_FRESHNESS_BUCKETS
+ */
+ private static final String JOBS_DEFERRED_DATA = "jbd";
private static final String KERNEL_WAKELOCK_DATA = "kwl";
private static final String WAKEUP_REASON_DATA = "wr";
private static final String NETWORK_DATA = "nt";
public static final String UID_TIMES_TYPE_ALL = "A";
/**
+ * These are the thresholds for bucketing last time since a job was run for an app
+ * that just moved to ACTIVE due to a launch. So if the last time a job ran was less
+ * than 30 minutes ago, then it's reasonably fresh, 2 hours ago, not so fresh and so
+ * on.
+ */
+ public static final long[] JOB_FRESHNESS_BUCKETS = {
+ 1 * 60 * 60 * 1000L,
+ 2 * 60 * 60 * 1000L,
+ 6 * 60 * 60 * 1000L,
+ Long.MAX_VALUE
+ };
+
+ /**
* State for keeping track of counting information.
*/
public static abstract class Counter {
*/
public abstract long getWifiRadioApWakeupCount(int which);
+ /**
+ * Appends the deferred jobs data to the StringBuilder passed in, in checkin format
+ * @param sb StringBuilder that can be overwritten with the deferred jobs data
+ * @param which one of STATS_*
+ */
+ public abstract void getDeferredJobsCheckinLineLocked(StringBuilder sb, int which);
+
+ /**
+ * Appends the deferred jobs data to the StringBuilder passed in
+ * @param sb StringBuilder that can be overwritten with the deferred jobs data
+ * @param which one of STATS_*
+ */
+ public abstract void getDeferredJobsLineLocked(StringBuilder sb, int which);
+
public static abstract class Sensor {
/*
* FIXME: it's not correct to use this magic value because it
}
}
+ // Dump deferred jobs stats
+ u.getDeferredJobsCheckinLineLocked(sb, which);
+ if (sb.length() > 0) {
+ dumpLine(pw, uid, category, JOBS_DEFERRED_DATA, sb.toString());
+ }
+
dumpTimer(pw, uid, category, FLASHLIGHT_DATA, u.getFlashlightTurnedOnTimer(),
rawRealtime, which);
dumpTimer(pw, uid, category, CAMERA_DATA, u.getCameraTurnedOnTimer(),
}
}
+ u.getDeferredJobsLineLocked(sb, which);
+ if (sb.length() > 0) {
+ pw.print(" Jobs deferred on launch "); pw.println(sb.toString());
+ }
+
uidActivity |= printTimer(pw, sb, u.getFlashlightTurnedOnTimer(), rawRealtime, which,
prefix, "Flashlight");
uidActivity |= printTimer(pw, sb, u.getCameraTurnedOnTimer(), rawRealtime, which,
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 174 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 175 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
* @param out the Parcel to be written to.
* @param counter a Counter, or null.
*/
- public static void writeCounterToParcel(Parcel out, Counter counter) {
+ public static void writeCounterToParcel(Parcel out, @Nullable Counter counter) {
if (counter == null) {
out.writeInt(0); // indicates null
return;
counter.writeToParcel(out);
}
+ /**
+ * Reads a Counter that was written using {@link #writeCounterToParcel(Parcel, Counter)}.
+ * @param timeBase the timebase to assign to the Counter
+ * @param in the parcel to read from
+ * @return the Counter or null.
+ */
+ public static @Nullable Counter readCounterFromParcel(TimeBase timeBase, Parcel in) {
+ if (in.readInt() == 0) {
+ return null;
+ }
+ return new Counter(timeBase, in);
+ }
+
@Override
public int getCountLocked(int which) {
int val = mCount.get();
addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_FINISH, name, uid);
}
+ public void noteJobsDeferredLocked(int uid, int numDeferred, long sinceLast) {
+ uid = mapUid(uid);
+ getUidStatsLocked(uid).noteJobsDeferredLocked(numDeferred, sinceLast);
+ }
+
public void noteAlarmStartLocked(String name, WorkSource workSource, int uid) {
noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_START, name, workSource, uid);
}
final ArrayMap<String, SparseIntArray> mJobCompletions = new ArrayMap<>();
/**
+ * Count of app launch events that had associated deferred job counts or info about
+ * last time a job was run.
+ */
+ Counter mJobsDeferredEventCount;
+
+ /**
+ * Count of deferred jobs that were pending when the app was launched or brought to
+ * the foreground through a user interaction.
+ */
+ Counter mJobsDeferredCount;
+
+ /**
+ * Sum of time since the last time a job was run for this app before it was launched.
+ */
+ LongSamplingCounter mJobsFreshnessTimeMs;
+
+ /**
+ * Array of counts of instances where the time since the last job was run for the app
+ * fell within one of the thresholds in {@link #JOB_FRESHNESS_BUCKETS}.
+ */
+ final Counter[] mJobsFreshnessBuckets;
+
+ /**
* The statistics we have collected for this uid's sensor activations.
*/
final SparseArray<Sensor> mSensorStats = new SparseArray<>();
mWifiMulticastTimer = new StopwatchTimer(mBsi.mClocks, this, WIFI_MULTICAST_ENABLED,
mBsi.mWifiMulticastTimers, mBsi.mOnBatteryTimeBase);
mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
+ mJobsDeferredEventCount = new Counter(mBsi.mOnBatteryTimeBase);
+ mJobsDeferredCount = new Counter(mBsi.mOnBatteryTimeBase);
+ mJobsFreshnessTimeMs = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
+ mJobsFreshnessBuckets = new Counter[JOB_FRESHNESS_BUCKETS.length];
}
@VisibleForTesting
return 0;
}
+ @Override
+ public void getDeferredJobsCheckinLineLocked(StringBuilder sb, int which) {
+ sb.setLength(0);
+ final int deferredEventCount = mJobsDeferredEventCount.getCountLocked(which);
+ if (deferredEventCount == 0) {
+ return;
+ }
+ final int deferredCount = mJobsDeferredCount.getCountLocked(which);
+ final long averageLatency = deferredEventCount != 0
+ ? mJobsFreshnessTimeMs.getCountLocked(which) / deferredEventCount
+ : 0L;
+ sb.append(deferredEventCount); sb.append(',');
+ sb.append(deferredCount); sb.append(',');
+ sb.append(averageLatency);
+ for (int i = 0; i < JOB_FRESHNESS_BUCKETS.length; i++) {
+ if (mJobsFreshnessBuckets[i] == null) {
+ sb.append(",0");
+ } else {
+ sb.append(",");
+ sb.append(mJobsFreshnessBuckets[i].getCountLocked(which));
+ }
+ }
+ }
+
+ @Override
+ public void getDeferredJobsLineLocked(StringBuilder sb, int which) {
+ sb.setLength(0);
+ final int deferredEventCount = mJobsDeferredEventCount.getCountLocked(which);
+ if (deferredEventCount == 0) {
+ return;
+ }
+ final int deferredCount = mJobsDeferredCount.getCountLocked(which);
+ final long averageLatency = deferredEventCount != 0
+ ? mJobsFreshnessTimeMs.getCountLocked(which) / deferredEventCount
+ : 0L;
+ sb.append("times="); sb.append(deferredEventCount); sb.append(", ");
+ sb.append("count="); sb.append(deferredCount); sb.append(", ");
+ sb.append("avgLatency="); sb.append(averageLatency); sb.append(", ");
+ for (int i = 0; i < JOB_FRESHNESS_BUCKETS.length; i++) {
+ sb.append("<"); sb.append(JOB_FRESHNESS_BUCKETS[i]); sb.append("ms=");
+ if (mJobsFreshnessBuckets[i] == null) {
+ sb.append("0");
+ } else {
+ sb.append(mJobsFreshnessBuckets[i].getCountLocked(which));
+ }
+ sb.append(" ");
+ }
+ }
+
void initNetworkActivityLocked() {
mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
}
mJobStats.cleanup();
mJobCompletions.clear();
+
+ mJobsDeferredEventCount.reset(false);
+ mJobsDeferredCount.reset(false);
+ mJobsFreshnessTimeMs.reset(false);
+ for (int ij = 0; ij < JOB_FRESHNESS_BUCKETS.length; ij++) {
+ if (mJobsFreshnessBuckets[ij] != null) {
+ mJobsFreshnessBuckets[ij].reset(false);
+ }
+ }
+
for (int ise=mSensorStats.size()-1; ise>=0; ise--) {
Sensor s = mSensorStats.valueAt(ise);
if (s.reset()) {
active = true;
}
}
+
for (int ip=mProcessStats.size()-1; ip>=0; ip--) {
Proc proc = mProcessStats.valueAt(ip);
proc.detach();
writeJobCompletionsToParcelLocked(out);
+ mJobsDeferredEventCount.writeToParcel(out);
+ mJobsDeferredCount.writeToParcel(out);
+ mJobsFreshnessTimeMs.writeToParcel(out);
+ for (int i = 0; i < JOB_FRESHNESS_BUCKETS.length; i++) {
+ Counter.writeCounterToParcel(out, mJobsFreshnessBuckets[i]);
+ }
+
int NSE = mSensorStats.size();
out.writeInt(NSE);
for (int ise=0; ise<NSE; ise++) {
readJobCompletionsFromParcelLocked(in);
+ mJobsDeferredEventCount = new Counter(mBsi.mOnBatteryTimeBase, in);
+ mJobsDeferredCount = new Counter(mBsi.mOnBatteryTimeBase, in);
+ mJobsFreshnessTimeMs = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+ for (int i = 0; i < JOB_FRESHNESS_BUCKETS.length; i++) {
+ mJobsFreshnessBuckets[i] = Counter.readCounterFromParcel(mBsi.mOnBatteryTimeBase,
+ in);
+ }
+
int numSensors = in.readInt();
mSensorStats.clear();
for (int k = 0; k < numSensors; k++) {
}
}
+ public void noteJobsDeferredLocked(int numDeferred, long sinceLast) {
+ mJobsDeferredEventCount.addAtomic(1);
+ mJobsDeferredCount.addAtomic(numDeferred);
+ if (sinceLast != 0) {
+ // Add the total time, which can be divided by the event count to get an average
+ mJobsFreshnessTimeMs.addCountLocked(sinceLast);
+ // Also keep track of how many times there were in these different buckets.
+ for (int i = 0; i < JOB_FRESHNESS_BUCKETS.length; i++) {
+ if (sinceLast < JOB_FRESHNESS_BUCKETS[i]) {
+ if (mJobsFreshnessBuckets[i] == null) {
+ mJobsFreshnessBuckets[i] = new Counter(
+ mBsi.mOnBatteryTimeBase);
+ }
+ mJobsFreshnessBuckets[i].addAtomic(1);
+ break;
+ }
+ }
+ }
+ }
+
/**
* The statistics associated with a particular wake lock.
*/
u.readJobCompletionsFromParcelLocked(in);
+ u.mJobsDeferredEventCount.readSummaryFromParcelLocked(in);
+ u.mJobsDeferredCount.readSummaryFromParcelLocked(in);
+ u.mJobsFreshnessTimeMs.readSummaryFromParcelLocked(in);
+ for (int i = 0; i < JOB_FRESHNESS_BUCKETS.length; i++) {
+ if (in.readInt() != 0) {
+ u.mJobsFreshnessBuckets[i] = new Counter(u.mBsi.mOnBatteryTimeBase);
+ u.mJobsFreshnessBuckets[i].readSummaryFromParcelLocked(in);
+ }
+ }
+
int NP = in.readInt();
if (NP > 1000) {
throw new ParcelFormatException("File corrupt: too many sensors " + NP);
u.writeJobCompletionsToParcelLocked(out);
+ u.mJobsDeferredEventCount.writeSummaryFromParcelLocked(out);
+ u.mJobsDeferredCount.writeSummaryFromParcelLocked(out);
+ u.mJobsFreshnessTimeMs.writeSummaryFromParcelLocked(out);
+ for (int i = 0; i < JOB_FRESHNESS_BUCKETS.length; i++) {
+ if (u.mJobsFreshnessBuckets[i] != null) {
+ u.mJobsFreshnessBuckets[i].writeSummaryFromParcelLocked(out);
+ } else {
+ out.writeInt(0);
+ }
+ }
+
int NSE = u.mSensorStats.size();
out.writeInt(NSE);
for (int ise=0; ise<NSE; ise++) {