blob: 4ba9dae87ca4924ff6126f622ce0a8e29480fe5e [file] [log] [blame]
Dianne Hackborn807de782016-04-07 17:54:41 -07001/*
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
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -070036 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
Dianne Hackborn807de782016-04-07 17:54:41 -070058 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
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700265 void dump(PrintWriter pw, String header, String prefix, long now, long nowEllapsed,
266 int filterUid) {
Dianne Hackborn807de782016-04-07 17:54:41 -0700267 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++) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700277 int uid = mEntries.keyAt(i);
278 if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
279 continue;
280 }
Dianne Hackborn807de782016-04-07 17:54:41 -0700281 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(" ");
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700286 UserHandle.formatUid(pw, uid);
Dianne Hackborn807de782016-04-07 17:54:41 -0700287 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 }
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700339 addEvent(EVENT_START_JOB, job.getSourceUid(), job.getBatteryName());
Dianne Hackborn807de782016-04-07 17:54:41 -0700340 }
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);
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700350 addEvent(EVENT_STOP_JOB, job.getSourceUid(), job.getBatteryName());
Dianne Hackborn807de782016-04-07 17:54:41 -0700351 }
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
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700371 public void dump(PrintWriter pw, String prefix, int filterUid) {
Dianne Hackborn807de782016-04-07 17:54:41 -0700372 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) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700384 mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowEllapsed, filterUid);
Dianne Hackborn807de782016-04-07 17:54:41 -0700385 pw.println();
386 }
387 }
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700388 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;
Dianne Hackborn807de782016-04-07 17:54:41 -0700422 }
423}