JobPackageTracker.java revision 807de78c072c5a40be7b12c656d641d9e73741d2
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 DataSet mCurDataSet = new DataSet(); 37 DataSet[] mLastDataSets = new DataSet[NUM_HISTORY]; 38 39 final static class PackageEntry { 40 long pastActiveTime; 41 long activeStartTime; 42 int activeCount; 43 boolean hadActive; 44 long pastActiveTopTime; 45 long activeTopStartTime; 46 int activeTopCount; 47 boolean hadActiveTop; 48 long pastPendingTime; 49 long pendingStartTime; 50 int pendingCount; 51 boolean hadPending; 52 53 public long getActiveTime(long now) { 54 long time = pastActiveTime; 55 if (activeCount > 0) { 56 time += now - activeStartTime; 57 } 58 return time; 59 } 60 61 public long getActiveTopTime(long now) { 62 long time = pastActiveTopTime; 63 if (activeTopCount > 0) { 64 time += now - activeTopStartTime; 65 } 66 return time; 67 } 68 69 public long getPendingTime(long now) { 70 long time = pastPendingTime; 71 if (pendingCount > 0) { 72 time += now - pendingStartTime; 73 } 74 return time; 75 } 76 } 77 78 final static class DataSet { 79 final SparseArray<ArrayMap<String, PackageEntry>> mEntries = new SparseArray<>(); 80 final long mStartUptimeTime; 81 final long mStartElapsedTime; 82 final long mStartClockTime; 83 long mSummedTime; 84 85 public DataSet(DataSet otherTimes) { 86 mStartUptimeTime = otherTimes.mStartUptimeTime; 87 mStartElapsedTime = otherTimes.mStartElapsedTime; 88 mStartClockTime = otherTimes.mStartClockTime; 89 } 90 91 public DataSet() { 92 mStartUptimeTime = SystemClock.uptimeMillis(); 93 mStartElapsedTime = SystemClock.elapsedRealtime(); 94 mStartClockTime = System.currentTimeMillis(); 95 } 96 97 private PackageEntry getOrCreateEntry(int uid, String pkg) { 98 ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid); 99 if (uidMap == null) { 100 uidMap = new ArrayMap<>(); 101 mEntries.put(uid, uidMap); 102 } 103 PackageEntry entry = uidMap.get(pkg); 104 if (entry == null) { 105 entry = new PackageEntry(); 106 uidMap.put(pkg, entry); 107 } 108 return entry; 109 } 110 111 public PackageEntry getEntry(int uid, String pkg) { 112 ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid); 113 if (uidMap == null) { 114 return null; 115 } 116 return uidMap.get(pkg); 117 } 118 119 long getTotalTime(long now) { 120 if (mSummedTime > 0) { 121 return mSummedTime; 122 } 123 return now - mStartUptimeTime; 124 } 125 126 void incPending(int uid, String pkg, long now) { 127 PackageEntry pe = getOrCreateEntry(uid, pkg); 128 if (pe.pendingCount == 0) { 129 pe.pendingStartTime = now; 130 } 131 pe.pendingCount++; 132 } 133 134 void decPending(int uid, String pkg, long now) { 135 PackageEntry pe = getOrCreateEntry(uid, pkg); 136 if (pe.pendingCount == 1) { 137 pe.pastPendingTime += now - pe.pendingStartTime; 138 } 139 pe.pendingCount--; 140 } 141 142 void incActive(int uid, String pkg, long now) { 143 PackageEntry pe = getOrCreateEntry(uid, pkg); 144 if (pe.activeCount == 0) { 145 pe.activeStartTime = now; 146 } 147 pe.activeCount++; 148 } 149 150 void decActive(int uid, String pkg, long now) { 151 PackageEntry pe = getOrCreateEntry(uid, pkg); 152 if (pe.activeCount == 1) { 153 pe.pastActiveTime += now - pe.activeStartTime; 154 } 155 pe.activeCount--; 156 } 157 158 void incActiveTop(int uid, String pkg, long now) { 159 PackageEntry pe = getOrCreateEntry(uid, pkg); 160 if (pe.activeTopCount == 0) { 161 pe.activeTopStartTime = now; 162 } 163 pe.activeTopCount++; 164 } 165 166 void decActiveTop(int uid, String pkg, long now) { 167 PackageEntry pe = getOrCreateEntry(uid, pkg); 168 if (pe.activeTopCount == 1) { 169 pe.pastActiveTopTime += now - pe.activeTopStartTime; 170 } 171 pe.activeTopCount--; 172 } 173 174 void finish(DataSet next, long now) { 175 for (int i = mEntries.size() - 1; i >= 0; i--) { 176 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); 177 for (int j = uidMap.size() - 1; j >= 0; j--) { 178 PackageEntry pe = uidMap.valueAt(j); 179 if (pe.activeCount > 0 || pe.activeTopCount > 0 || pe.pendingCount > 0) { 180 // Propagate existing activity in to next data set. 181 PackageEntry nextPe = next.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j)); 182 nextPe.activeStartTime = now; 183 nextPe.activeCount = pe.activeCount; 184 nextPe.activeTopStartTime = now; 185 nextPe.activeTopCount = pe.activeTopCount; 186 nextPe.pendingStartTime = now; 187 nextPe.pendingCount = pe.pendingCount; 188 // Finish it off. 189 if (pe.activeCount > 0) { 190 pe.pastActiveTime += now - pe.activeStartTime; 191 pe.activeCount = 0; 192 } 193 if (pe.activeTopCount > 0) { 194 pe.pastActiveTopTime += now - pe.activeTopStartTime; 195 pe.activeTopCount = 0; 196 } 197 if (pe.pendingCount > 0) { 198 pe.pastPendingTime += now - pe.pendingStartTime; 199 pe.pendingCount = 0; 200 } 201 } 202 } 203 } 204 } 205 206 void addTo(DataSet out, long now) { 207 out.mSummedTime += getTotalTime(now); 208 for (int i = mEntries.size() - 1; i >= 0; i--) { 209 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); 210 for (int j = uidMap.size() - 1; j >= 0; j--) { 211 PackageEntry pe = uidMap.valueAt(j); 212 PackageEntry outPe = out.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j)); 213 outPe.pastActiveTime += pe.pastActiveTime; 214 outPe.pastActiveTopTime += pe.pastActiveTopTime; 215 outPe.pastPendingTime += pe.pastPendingTime; 216 if (pe.activeCount > 0) { 217 outPe.pastActiveTime += now - pe.activeStartTime; 218 outPe.hadActive = true; 219 } 220 if (pe.activeTopCount > 0) { 221 outPe.pastActiveTopTime += now - pe.activeTopStartTime; 222 outPe.hadActiveTop = true; 223 } 224 if (pe.pendingCount > 0) { 225 outPe.pastPendingTime += now - pe.pendingStartTime; 226 outPe.hadPending = true; 227 } 228 } 229 } 230 } 231 232 void printDuration(PrintWriter pw, long period, long duration, String suffix) { 233 float fraction = duration / (float) period; 234 int percent = (int) ((fraction * 100) + .5f); 235 if (percent > 0) { 236 pw.print(" "); 237 pw.print(percent); 238 pw.print("% "); 239 pw.print(suffix); 240 } 241 } 242 243 void dump(PrintWriter pw, String header, String prefix, long now, long nowEllapsed) { 244 final long period = getTotalTime(now); 245 pw.print(prefix); pw.print(header); pw.print(" at "); 246 pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString()); 247 pw.print(" ("); 248 TimeUtils.formatDuration(mStartElapsedTime, nowEllapsed, pw); 249 pw.print(") over "); 250 TimeUtils.formatDuration(period, pw); 251 pw.println(":"); 252 final int NE = mEntries.size(); 253 for (int i = 0; i < NE; i++) { 254 ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); 255 final int NP = uidMap.size(); 256 for (int j = 0; j < NP; j++) { 257 PackageEntry pe = uidMap.valueAt(j); 258 pw.print(prefix); pw.print(" "); 259 UserHandle.formatUid(pw, mEntries.keyAt(i)); 260 pw.print(" / "); pw.print(uidMap.keyAt(j)); 261 pw.print(":"); 262 printDuration(pw, period, pe.getPendingTime(now), "pending"); 263 printDuration(pw, period, pe.getActiveTime(now), "active"); 264 printDuration(pw, period, pe.getActiveTopTime(now), "active-top"); 265 if (pe.pendingCount > 0 || pe.hadPending) { 266 pw.print(" (pending)"); 267 } 268 if (pe.activeCount > 0 || pe.hadActive) { 269 pw.print(" (active)"); 270 } 271 if (pe.activeTopCount > 0 || pe.hadActiveTop) { 272 pw.print(" (active-top)"); 273 } 274 pw.println(); 275 } 276 } 277 } 278 } 279 280 void rebatchIfNeeded(long now) { 281 long totalTime = mCurDataSet.getTotalTime(now); 282 if (totalTime > BATCHING_TIME) { 283 DataSet last = mCurDataSet; 284 last.mSummedTime = totalTime; 285 mCurDataSet = new DataSet(); 286 last.finish(mCurDataSet, now); 287 System.arraycopy(mLastDataSets, 0, mLastDataSets, 1, mLastDataSets.length-1); 288 mLastDataSets[0] = last; 289 } 290 } 291 292 public void notePending(JobStatus job) { 293 final long now = SystemClock.uptimeMillis(); 294 rebatchIfNeeded(now); 295 mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now); 296 } 297 298 public void noteNonpending(JobStatus job) { 299 final long now = SystemClock.uptimeMillis(); 300 mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now); 301 rebatchIfNeeded(now); 302 } 303 304 public void noteActive(JobStatus job) { 305 final long now = SystemClock.uptimeMillis(); 306 rebatchIfNeeded(now); 307 if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { 308 mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now); 309 } else { 310 mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now); 311 } 312 } 313 314 public void noteInactive(JobStatus job) { 315 final long now = SystemClock.uptimeMillis(); 316 if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { 317 mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now); 318 } else { 319 mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now); 320 } 321 rebatchIfNeeded(now); 322 } 323 324 public float getLoadFactor(JobStatus job) { 325 final int uid = job.getSourceUid(); 326 final String pkg = job.getSourcePackageName(); 327 PackageEntry cur = mCurDataSet.getEntry(uid, pkg); 328 PackageEntry last = mLastDataSets[0] != null ? mLastDataSets[0].getEntry(uid, pkg) : null; 329 if (cur == null && last == null) { 330 return 0; 331 } 332 final long now = SystemClock.uptimeMillis(); 333 long time = cur.getActiveTime(now) + cur.getPendingTime(now); 334 long period = mCurDataSet.getTotalTime(now); 335 if (last != null) { 336 time += last.getActiveTime(now) + last.getPendingTime(now); 337 period += mLastDataSets[0].getTotalTime(now); 338 } 339 return time / (float)period; 340 } 341 342 public void dump(PrintWriter pw, String prefix) { 343 final long now = SystemClock.uptimeMillis(); 344 final long nowEllapsed = SystemClock.elapsedRealtime(); 345 final DataSet total; 346 if (mLastDataSets[0] != null) { 347 total = new DataSet(mLastDataSets[0]); 348 mLastDataSets[0].addTo(total, now); 349 } else { 350 total = new DataSet(mCurDataSet); 351 } 352 mCurDataSet.addTo(total, now); 353 for (int i = 1; i < mLastDataSets.length; i++) { 354 if (mLastDataSets[i] != null) { 355 mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowEllapsed); 356 pw.println(); 357 } 358 } 359 total.dump(pw, "Current stats", prefix, now, nowEllapsed); 360 } 361} 362