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