1/*
2 * Copyright (C) 2017 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.systemui.util.leak;
18
19import android.os.SystemClock;
20
21import java.io.PrintWriter;
22import java.lang.ref.WeakReference;
23import java.util.Collection;
24import java.util.Map;
25import java.util.function.Predicate;
26
27/**
28 * Tracks the size of collections.
29 */
30public class TrackedCollections {
31    private static final long MILLIS_IN_MINUTE = 60 * 1000;
32    private static final long HALFWAY_DELAY = 30 * MILLIS_IN_MINUTE;
33
34    private final WeakIdentityHashMap<Collection<?>, CollectionState> mCollections
35            = new WeakIdentityHashMap<>();
36
37    /**
38     * @see LeakDetector#trackCollection(Collection, String)
39     */
40    public synchronized void track(Collection<?> collection, String tag) {
41        CollectionState collectionState = mCollections.get(collection);
42        if (collectionState == null) {
43            collectionState = new CollectionState();
44            collectionState.tag = tag;
45            collectionState.startUptime = SystemClock.uptimeMillis();
46            mCollections.put(collection, collectionState);
47        }
48        if (collectionState.halfwayCount == -1
49                && SystemClock.uptimeMillis() - collectionState.startUptime > HALFWAY_DELAY) {
50            collectionState.halfwayCount = collectionState.lastCount;
51        }
52        collectionState.lastCount = collection.size();
53        collectionState.lastUptime = SystemClock.uptimeMillis();
54    }
55
56    private static class CollectionState {
57        String tag;
58        long startUptime;
59        /** The number of elements in the collection at startUptime + HALFWAY_DELAY */
60        int halfwayCount = -1;
61        /** The number of elements in the collection at lastUptime */
62        int lastCount = -1;
63        long lastUptime;
64
65        /**
66         * Dump statistics about the tracked collection:
67         * - the tag
68         * - average elements inserted per hour during
69         *   - the first 30min of its existence
70         *   - after the first 30min
71         *   - overall
72         * - the current size of the collection
73         */
74        void dump(PrintWriter pw) {
75            long now = SystemClock.uptimeMillis();
76
77            pw.format("%s: %.2f (start-30min) / %.2f (30min-now) / %.2f (start-now)"
78                            + " (growth rate in #/hour); %d (current size)",
79                    tag,
80                    ratePerHour(startUptime, 0, startUptime + HALFWAY_DELAY, halfwayCount),
81                    ratePerHour(startUptime + HALFWAY_DELAY, halfwayCount, now, lastCount),
82                    ratePerHour(startUptime, 0, now, lastCount),
83                    lastCount);
84        }
85
86        private float ratePerHour(long uptime1, int count1, long uptime2, int count2) {
87            if (uptime1 >= uptime2 || count1 < 0 || count2 < 0) {
88                return Float.NaN;
89            }
90            return ((float) count2 - count1) / (uptime2 - uptime1) * 60 * MILLIS_IN_MINUTE;
91        }
92    }
93
94    public synchronized void dump(PrintWriter pw, Predicate<Collection<?>> filter) {
95        for (Map.Entry<WeakReference<Collection<?>>, CollectionState> entry
96                : mCollections.entrySet()) {
97            Collection<?> key = entry.getKey().get();
98            if (filter == null || key != null && filter.test(key)) {
99                entry.getValue().dump(pw);
100                pw.println();
101            }
102        }
103    }
104}
105