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