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