JobPackageTracker.java revision ef3aa6ee53c5e4f1c50dd5a9b5821c54e449d4b3
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 android.app.job.JobInfo; 20import android.os.SystemClock; 21import android.os.UserHandle; 22import android.text.format.DateFormat; 23import android.util.ArrayMap; 24import android.util.SparseArray; 25import android.util.TimeUtils; 26import com.android.server.job.controllers.JobStatus; 27 28import java.io.PrintWriter; 29 30public final class JobPackageTracker { 31 // We batch every 30 minutes. 32 static final long BATCHING_TIME = 30*60*1000; 33 // Number of historical data sets we keep. 34 static final int NUM_HISTORY = 5; 35 36 private static final int EVENT_BUFFER_SIZE = 50; 37 38 public static final int EVENT_NULL = 0; 39 public static final int EVENT_START_JOB = 1; 40 public static final int EVENT_STOP_JOB = 2; 41 42 private int[] mEventCmds = new int[EVENT_BUFFER_SIZE]; 43 private long[] mEventTimes = new long[EVENT_BUFFER_SIZE]; 44 private int[] mEventUids = new int[EVENT_BUFFER_SIZE]; 45 private String[] mEventTags = new String[EVENT_BUFFER_SIZE]; 46 47 public void addEvent(int cmd, int uid, String tag) { 48 System.arraycopy(mEventCmds, 0, mEventCmds, 1, EVENT_BUFFER_SIZE - 1); 49 System.arraycopy(mEventTimes, 0, mEventTimes, 1, EVENT_BUFFER_SIZE - 1); 50 System.arraycopy(mEventUids, 0, mEventUids, 1, EVENT_BUFFER_SIZE - 1); 51 System.arraycopy(mEventTags, 0, mEventTags, 1, EVENT_BUFFER_SIZE - 1); 52 mEventCmds[0] = cmd; 53 mEventTimes[0] = SystemClock.elapsedRealtime(); 54 mEventUids[0] = uid; 55 mEventTags[0] = tag; 56 } 57 58 DataSet mCurDataSet = new DataSet(); 59 DataSet[] mLastDataSets = new DataSet[NUM_HISTORY]; 60 61 final static class PackageEntry { 62 long pastActiveTime; 63 long activeStartTime; 64 int activeCount; 65 boolean hadActive; 66 long pastActiveTopTime; 67 long activeTopStartTime; 68 int activeTopCount; 69 boolean hadActiveTop; 70 long pastPendingTime; 71 long pendingStartTime; 72 int pendingCount; 73 boolean hadPending; 74 75 public long getActiveTime(long now) { 76 long time = pastActiveTime; 77 if (activeCount > 0) { 78 time += now - activeStartTime; 79 } 80 return time; 81 } 82 83 public long getActiveTopTime(long now) { 84 long time = pastActiveTopTime; 85 if (activeTopCount > 0) { 86 time += now - activeTopStartTime; 87 } 88 return time; 89 } 90 91 public long getPendingTime(long now) { 92 long time = pastPendingTime; 93 if (pendingCount > 0) { 94 time += now - pendingStartTime; 95 } 96 return time; 97 } 98 } 99 100 final static class DataSet { 101 final SparseArray<ArrayMap<String, PackageEntry>> mEntries = new SparseArray<>(); 102 final long mStartUptimeTime; 103 final long mStartElapsedTime; 104 final long mStartClockTime; 105 long mSummedTime; 106 107 public DataSet(DataSet otherTimes) { 108 mStartUptimeTime = otherTimes.mStartUptimeTime; 109 mStartElapsedTime = otherTimes.mStartElapsedTime; 110 mStartClockTime = otherTimes.mStartClockTime; 111 } 112 113 public DataSet() { 114 mStartUptimeTime = SystemClock.uptimeMillis(); 115 mStartElapsedTime = SystemClock.elapsedRealtime(); 116 mStartClockTime = System.currentTimeMillis(); 117 } 118 119 private PackageEntry getOrCreateEntry(int uid, String pkg) { 120 ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid); 121 if (uidMap == null) { 122 uidMap = new ArrayMap<>(); 123 mEntries.put(uid, uidMap); 124 } 125 PackageEntry entry = uidMap.get(pkg); 126 if (entry == null) { 127 entry = new PackageEntry(); 128 uidMap.put(pkg, entry); 129 } 130 return entry; 131 } 132 133 public PackageEntry getEntry(int uid, String pkg) { 134 ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid); 135 if (uidMap == null) { 136 return null; 137 } 138 return uidMap.get(pkg); 139 } 140 141 long getTotalTime(long now) { 142 if (mSummedTime > 0) { 143 return mSummedTime; 144 } 145 return now - mStartUptimeTime; 146 } 147 148 void incPending(int uid, String pkg, long now) { 149 PackageEntry pe = getOrCreateEntry(uid, pkg); 150 if (pe.pendingCount == 0) { 151 pe.pendingStartTime = now; 152 } 153 pe.pendingCount++; 154 } 155 156 void decPending(int uid, String pkg, long now) { 157 PackageEntry pe = getOrCreateEntry(uid, pkg); 158 if (pe.pendingCount == 1) { 159 pe.pastPendingTime += now - pe.pendingStartTime; 160 } 161 pe.pendingCount--; 162 } 163 164 void incActive(int uid, String pkg, long now) { 165 PackageEntry pe = getOrCreateEntry(uid, pkg); 166 if (pe.activeCount == 0) { 167 pe.activeStartTime = now; 168 } 169 pe.activeCount++; 170 } 171 172 void decActive(int uid, String pkg, long now) { 173 PackageEntry pe = getOrCreateEntry(uid, pkg); 174 if (pe.activeCount == 1) { 175 pe.pastActiveTime += now - pe.activeStartTime; 176 } 177 pe.activeCount--; 178 } 179 180 void incActiveTop(int uid, String pkg, long now) { 181 PackageEntry pe = getOrCreateEntry(uid, pkg); 182 if (pe.activeTopCount == 0) { 183 pe.activeTopStartTime = now; 184 } 185 pe.activeTopCount++; 186 } 187 188 void decActiveTop(int uid, String pkg, long now) { 189 PackageEntry pe = getOrCreateEntry(uid, pkg); 190 if (pe.activeTopCount == 1) { 191 pe.pastActiveTopTime += now - pe.activeTopStartTime; 192 } 193 pe.activeTopCount--; 194 } 195 196 void finish(DataSet next, long now) { 197 for (int i = mEntries.size() - 1; i >= 0; i--) { 198 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); 199 for (int j = uidMap.size() - 1; j >= 0; j--) { 200 PackageEntry pe = uidMap.valueAt(j); 201 if (pe.activeCount > 0 || pe.activeTopCount > 0 || pe.pendingCount > 0) { 202 // Propagate existing activity in to next data set. 203 PackageEntry nextPe = next.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j)); 204 nextPe.activeStartTime = now; 205 nextPe.activeCount = pe.activeCount; 206 nextPe.activeTopStartTime = now; 207 nextPe.activeTopCount = pe.activeTopCount; 208 nextPe.pendingStartTime = now; 209 nextPe.pendingCount = pe.pendingCount; 210 // Finish it off. 211 if (pe.activeCount > 0) { 212 pe.pastActiveTime += now - pe.activeStartTime; 213 pe.activeCount = 0; 214 } 215 if (pe.activeTopCount > 0) { 216 pe.pastActiveTopTime += now - pe.activeTopStartTime; 217 pe.activeTopCount = 0; 218 } 219 if (pe.pendingCount > 0) { 220 pe.pastPendingTime += now - pe.pendingStartTime; 221 pe.pendingCount = 0; 222 } 223 } 224 } 225 } 226 } 227 228 void addTo(DataSet out, long now) { 229 out.mSummedTime += getTotalTime(now); 230 for (int i = mEntries.size() - 1; i >= 0; i--) { 231 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); 232 for (int j = uidMap.size() - 1; j >= 0; j--) { 233 PackageEntry pe = uidMap.valueAt(j); 234 PackageEntry outPe = out.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j)); 235 outPe.pastActiveTime += pe.pastActiveTime; 236 outPe.pastActiveTopTime += pe.pastActiveTopTime; 237 outPe.pastPendingTime += pe.pastPendingTime; 238 if (pe.activeCount > 0) { 239 outPe.pastActiveTime += now - pe.activeStartTime; 240 outPe.hadActive = true; 241 } 242 if (pe.activeTopCount > 0) { 243 outPe.pastActiveTopTime += now - pe.activeTopStartTime; 244 outPe.hadActiveTop = true; 245 } 246 if (pe.pendingCount > 0) { 247 outPe.pastPendingTime += now - pe.pendingStartTime; 248 outPe.hadPending = true; 249 } 250 } 251 } 252 } 253 254 void printDuration(PrintWriter pw, long period, long duration, String suffix) { 255 float fraction = duration / (float) period; 256 int percent = (int) ((fraction * 100) + .5f); 257 if (percent > 0) { 258 pw.print(" "); 259 pw.print(percent); 260 pw.print("% "); 261 pw.print(suffix); 262 } 263 } 264 265 void dump(PrintWriter pw, String header, String prefix, long now, long nowEllapsed, 266 int filterUid) { 267 final long period = getTotalTime(now); 268 pw.print(prefix); pw.print(header); pw.print(" at "); 269 pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString()); 270 pw.print(" ("); 271 TimeUtils.formatDuration(mStartElapsedTime, nowEllapsed, pw); 272 pw.print(") over "); 273 TimeUtils.formatDuration(period, pw); 274 pw.println(":"); 275 final int NE = mEntries.size(); 276 for (int i = 0; i < NE; i++) { 277 int uid = mEntries.keyAt(i); 278 if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) { 279 continue; 280 } 281 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); 282 final int NP = uidMap.size(); 283 for (int j = 0; j < NP; j++) { 284 PackageEntry pe = uidMap.valueAt(j); 285 pw.print(prefix); pw.print(" "); 286 UserHandle.formatUid(pw, uid); 287 pw.print(" / "); pw.print(uidMap.keyAt(j)); 288 pw.print(":"); 289 printDuration(pw, period, pe.getPendingTime(now), "pending"); 290 printDuration(pw, period, pe.getActiveTime(now), "active"); 291 printDuration(pw, period, pe.getActiveTopTime(now), "active-top"); 292 if (pe.pendingCount > 0 || pe.hadPending) { 293 pw.print(" (pending)"); 294 } 295 if (pe.activeCount > 0 || pe.hadActive) { 296 pw.print(" (active)"); 297 } 298 if (pe.activeTopCount > 0 || pe.hadActiveTop) { 299 pw.print(" (active-top)"); 300 } 301 pw.println(); 302 } 303 } 304 } 305 } 306 307 void rebatchIfNeeded(long now) { 308 long totalTime = mCurDataSet.getTotalTime(now); 309 if (totalTime > BATCHING_TIME) { 310 DataSet last = mCurDataSet; 311 last.mSummedTime = totalTime; 312 mCurDataSet = new DataSet(); 313 last.finish(mCurDataSet, now); 314 System.arraycopy(mLastDataSets, 0, mLastDataSets, 1, mLastDataSets.length-1); 315 mLastDataSets[0] = last; 316 } 317 } 318 319 public void notePending(JobStatus job) { 320 final long now = SystemClock.uptimeMillis(); 321 rebatchIfNeeded(now); 322 mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now); 323 } 324 325 public void noteNonpending(JobStatus job) { 326 final long now = SystemClock.uptimeMillis(); 327 mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now); 328 rebatchIfNeeded(now); 329 } 330 331 public void noteActive(JobStatus job) { 332 final long now = SystemClock.uptimeMillis(); 333 rebatchIfNeeded(now); 334 if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { 335 mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now); 336 } else { 337 mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now); 338 } 339 addEvent(EVENT_START_JOB, job.getSourceUid(), job.getBatteryName()); 340 } 341 342 public void noteInactive(JobStatus job) { 343 final long now = SystemClock.uptimeMillis(); 344 if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { 345 mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now); 346 } else { 347 mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now); 348 } 349 rebatchIfNeeded(now); 350 addEvent(EVENT_STOP_JOB, job.getSourceUid(), job.getBatteryName()); 351 } 352 353 public float getLoadFactor(JobStatus job) { 354 final int uid = job.getSourceUid(); 355 final String pkg = job.getSourcePackageName(); 356 PackageEntry cur = mCurDataSet.getEntry(uid, pkg); 357 PackageEntry last = mLastDataSets[0] != null ? mLastDataSets[0].getEntry(uid, pkg) : null; 358 if (cur == null && last == null) { 359 return 0; 360 } 361 final long now = SystemClock.uptimeMillis(); 362 long time = cur.getActiveTime(now) + cur.getPendingTime(now); 363 long period = mCurDataSet.getTotalTime(now); 364 if (last != null) { 365 time += last.getActiveTime(now) + last.getPendingTime(now); 366 period += mLastDataSets[0].getTotalTime(now); 367 } 368 return time / (float)period; 369 } 370 371 public void dump(PrintWriter pw, String prefix, int filterUid) { 372 final long now = SystemClock.uptimeMillis(); 373 final long nowEllapsed = SystemClock.elapsedRealtime(); 374 final DataSet total; 375 if (mLastDataSets[0] != null) { 376 total = new DataSet(mLastDataSets[0]); 377 mLastDataSets[0].addTo(total, now); 378 } else { 379 total = new DataSet(mCurDataSet); 380 } 381 mCurDataSet.addTo(total, now); 382 for (int i = 1; i < mLastDataSets.length; i++) { 383 if (mLastDataSets[i] != null) { 384 mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowEllapsed, filterUid); 385 pw.println(); 386 } 387 } 388 total.dump(pw, "Current stats", prefix, now, nowEllapsed, filterUid); 389 } 390 391 public boolean dumpHistory(PrintWriter pw, String prefix, int filterUid) { 392 if (mEventCmds[0] == EVENT_NULL) { 393 return false; 394 } 395 pw.println(" Job history:"); 396 long now = SystemClock.elapsedRealtime(); 397 for (int i=EVENT_BUFFER_SIZE-1; i>=0; i--) { 398 int uid = mEventUids[i]; 399 if (filterUid != -1 && filterUid != UserHandle.getAppId(filterUid)) { 400 continue; 401 } 402 int cmd = mEventCmds[i]; 403 if (cmd == EVENT_NULL) { 404 continue; 405 } 406 String label; 407 switch (mEventCmds[i]) { 408 case EVENT_START_JOB: label = "START"; break; 409 case EVENT_STOP_JOB: label = " STOP"; break; 410 default: label = " ??"; break; 411 } 412 pw.print(prefix); 413 TimeUtils.formatDuration(mEventTimes[i]-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); 414 pw.print(" "); 415 pw.print(label); 416 pw.print(": "); 417 UserHandle.formatUid(pw, uid); 418 pw.print(" "); 419 pw.println(mEventTags[i]); 420 } 421 return true; 422 } 423} 424