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;
20import android.util.ArrayMap;
21
22import java.io.PrintWriter;
23import java.lang.ref.Reference;
24import java.lang.ref.ReferenceQueue;
25import java.lang.ref.WeakReference;
26import java.util.HashSet;
27import java.util.Map;
28
29/**
30 * Tracks objects that have been marked as garbage.
31 */
32public class TrackedGarbage {
33
34    /** Duration after which we consider garbage to be old. */
35    private static final long GARBAGE_COLLECTION_DEADLINE_MILLIS = 60000; // 1min
36
37    private final HashSet<LeakReference> mGarbage = new HashSet<>();
38    private final ReferenceQueue<Object> mRefQueue = new ReferenceQueue<>();
39    private final TrackedCollections mTrackedCollections;
40
41    public TrackedGarbage(TrackedCollections trackedCollections) {
42        mTrackedCollections = trackedCollections;
43    }
44
45    /**
46     * @see LeakDetector#trackGarbage(Object)
47     */
48    public synchronized void track(Object o) {
49        cleanUp();
50        mGarbage.add(new LeakReference(o, mRefQueue));
51        mTrackedCollections.track(mGarbage, "Garbage");
52    }
53
54    private void cleanUp() {
55        Reference<?> ref;
56        while ((ref = mRefQueue.poll()) != null) {
57            mGarbage.remove(ref);
58        }
59    }
60
61    /**
62     * A reference to something we consider leaked if it still has strong references.
63     *
64     * Helpful for finding potential leaks in a heapdump: Simply find an instance of
65     * LeakReference, find the object it refers to, then find a strong path to a GC root.
66     */
67    private static class LeakReference extends WeakReference<Object> {
68        private final Class<?> clazz;
69        private final long createdUptimeMillis;
70
71        LeakReference(Object t, ReferenceQueue<Object> queue) {
72            super(t, queue);
73            clazz = t.getClass();
74            createdUptimeMillis = SystemClock.uptimeMillis();
75        }
76    }
77
78    /**
79     * Dump statistics about the garbage.
80     *
81     * For each class, dumps the number of "garbage objects" that have not been collected yet.
82     * A large number of old instances indicates a probable leak.
83     */
84    public synchronized void dump(PrintWriter pw) {
85        cleanUp();
86
87        long now = SystemClock.uptimeMillis();
88
89        ArrayMap<Class<?>, Integer> acc = new ArrayMap<>();
90        ArrayMap<Class<?>, Integer> accOld = new ArrayMap<>();
91        for (LeakReference ref : mGarbage) {
92            acc.put(ref.clazz, acc.getOrDefault(ref.clazz, 0) + 1);
93            if (isOld(ref.createdUptimeMillis, now)) {
94                accOld.put(ref.clazz, accOld.getOrDefault(ref.clazz, 0) + 1);
95            }
96        }
97
98        for (Map.Entry<Class<?>, Integer> entry : acc.entrySet()) {
99            pw.print(entry.getKey().getName());
100            pw.print(": ");
101            pw.print(entry.getValue());
102            pw.print(" total, ");
103            pw.print(accOld.getOrDefault(entry.getKey(), 0));
104            pw.print(" old");
105            pw.println();
106        }
107    }
108
109    public synchronized int countOldGarbage() {
110        cleanUp();
111
112        long now = SystemClock.uptimeMillis();
113
114        int result = 0;
115        for (LeakReference ref : mGarbage) {
116            if (isOld(ref.createdUptimeMillis, now)) {
117                result++;
118            }
119        }
120        return result;
121    }
122
123    private boolean isOld(long createdUptimeMillis, long now) {
124        return createdUptimeMillis + GARBAGE_COLLECTION_DEADLINE_MILLIS < now;
125    }
126}
127