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.Build;
20
21import com.android.internal.annotations.VisibleForTesting;
22import com.android.internal.util.IndentingPrintWriter;
23import com.android.systemui.Dumpable;
24
25import java.io.FileDescriptor;
26import java.io.PrintWriter;
27import java.io.Writer;
28import java.util.Collection;
29
30/**
31 * Detects leaks.
32 */
33public class LeakDetector implements Dumpable {
34
35    public static final boolean ENABLED = Build.IS_DEBUGGABLE;
36
37    private final TrackedCollections mTrackedCollections;
38    private final TrackedGarbage mTrackedGarbage;
39    private final TrackedObjects mTrackedObjects;
40
41    @VisibleForTesting
42    public LeakDetector(TrackedCollections trackedCollections,
43            TrackedGarbage trackedGarbage,
44            TrackedObjects trackedObjects) {
45        mTrackedCollections = trackedCollections;
46        mTrackedGarbage = trackedGarbage;
47        mTrackedObjects = trackedObjects;
48    }
49
50    /**
51     * Tracks an instance that has a high leak risk (i.e. has complex ownership and references
52     * a large amount of memory).
53     *
54     * The LeakDetector will monitor and keep weak references to such instances, dump statistics
55     * about them in a bugreport, and in the future dump the heap if their count starts growing
56     * unreasonably.
57     *
58     * This should be called when the instance is first constructed.
59     */
60    public <T> void trackInstance(T object) {
61        if (mTrackedObjects != null) {
62            mTrackedObjects.track(object);
63        }
64    }
65
66    /**
67     * Tracks a collection that is at risk of leaking large objects, e.g. a collection of
68     * dynamically registered listeners.
69     *
70     * The LeakDetector will monitor and keep weak references to such collections, dump
71     * statistics about them in a bugreport, and in the future dump the heap if their size starts
72     * growing unreasonably.
73     *
74     * This should be called whenever the collection grows.
75     *
76     * @param tag A tag for labeling the collection in a bugreport
77     */
78    public <T> void trackCollection(Collection<T> collection, String tag) {
79        if (mTrackedCollections != null) {
80            mTrackedCollections.track(collection, tag);
81        }
82    }
83
84    /**
85     * Tracks an instance that should become garbage soon.
86     *
87     * The LeakDetector will monitor and keep weak references to such garbage, dump
88     * statistics about them in a bugreport, and in the future dump the heap if it is not
89     * collected reasonably soon.
90     *
91     * This should be called when the last strong reference to the instance is dropped.
92     */
93    public void trackGarbage(Object o) {
94        if (mTrackedGarbage != null) {
95            mTrackedGarbage.track(o);
96        }
97    }
98
99    TrackedGarbage getTrackedGarbage() {
100        return mTrackedGarbage;
101    }
102
103    @Override
104    public void dump(FileDescriptor df, PrintWriter w, String[] args) {
105        IndentingPrintWriter pw = new IndentingPrintWriter(w, "  ");
106
107        pw.println("SYSUI LEAK DETECTOR");
108        pw.increaseIndent();
109
110        if (mTrackedCollections != null && mTrackedGarbage != null) {
111            pw.println("TrackedCollections:");
112            pw.increaseIndent();
113            mTrackedCollections.dump(pw, (col) -> !TrackedObjects.isTrackedObject(col));
114            pw.decreaseIndent();
115            pw.println();
116
117            pw.println("TrackedObjects:");
118            pw.increaseIndent();
119            mTrackedCollections.dump(pw, TrackedObjects::isTrackedObject);
120            pw.decreaseIndent();
121            pw.println();
122
123            pw.print("TrackedGarbage:");
124            pw.increaseIndent();
125            mTrackedGarbage.dump(pw);
126            pw.decreaseIndent();
127        } else {
128            pw.println("disabled");
129        }
130        pw.decreaseIndent();
131        pw.println();
132    }
133
134    public static LeakDetector create() {
135        if (ENABLED) {
136            TrackedCollections collections = new TrackedCollections();
137            return new LeakDetector(collections, new TrackedGarbage(collections),
138                    new TrackedObjects(collections));
139        } else {
140            return new LeakDetector(null, null, null);
141        }
142    }
143}
144