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