1 /*
2  * Copyright (C) 2023 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 android.view;
18 
19 import static android.Manifest.permission.READ_FRAME_BUFFER;
20 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.content.Context;
26 import android.os.Build;
27 import android.os.SystemClock;
28 import android.os.SystemProperties;
29 import android.util.Log;
30 
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.util.GcUtils;
34 
35 import java.io.PrintWriter;
36 import java.util.ArrayList;
37 import java.util.Map;
38 import java.util.WeakHashMap;
39 
40 /**
41  * A thread-safe registry used to track surface controls that are active (not yet released) within a
42  * process, to help debug and identify leaks.
43  * @hide
44  */
45 public class SurfaceControlRegistry {
46     private static final String TAG = "SurfaceControlRegistry";
47 
48     /**
49      * An interface for processing the registered SurfaceControls when the threshold is exceeded.
50      */
51     public interface Reporter {
52         /**
53          * Called when the set of layers exceeds the max threshold.  This can be called on any
54          * thread, and must be handled synchronously.
55          */
onMaxLayersExceeded(WeakHashMap<SurfaceControl, Long> surfaceControls, int limit, PrintWriter pw)56         void onMaxLayersExceeded(WeakHashMap<SurfaceControl, Long> surfaceControls, int limit,
57                 PrintWriter pw);
58     }
59 
60     /**
61      * The default implementation of the reporter which logs the existing registered surfaces to
62      * logcat.
63      */
64     private static class DefaultReporter implements Reporter {
onMaxLayersExceeded(WeakHashMap<SurfaceControl, Long> surfaceControls, int limit, PrintWriter pw)65         public void onMaxLayersExceeded(WeakHashMap<SurfaceControl, Long> surfaceControls,
66                 int limit, PrintWriter pw) {
67             final long now = SystemClock.elapsedRealtime();
68             final ArrayList<Map.Entry<SurfaceControl, Long>> entries = new ArrayList<>();
69             for (Map.Entry<SurfaceControl, Long> entry : surfaceControls.entrySet()) {
70                 entries.add(entry);
71             }
72             // Sort entries by time registered when dumping
73             // TODO: Or should it sort by name?
74             entries.sort((o1, o2) -> (int) (o1.getValue() - o2.getValue()));
75             final int size = Math.min(entries.size(), limit);
76 
77             pw.println("SurfaceControlRegistry");
78             pw.println("----------------------");
79             pw.println("Listing oldest " + size + " of " + surfaceControls.size());
80             for (int i = 0; i < size; i++) {
81                 final Map.Entry<SurfaceControl, Long> entry = entries.get(i);
82                 final SurfaceControl sc = entry.getKey();
83                 if (sc == null) {
84                     // Just skip if the key has since been removed from the weak hash map
85                     continue;
86                 }
87 
88                 final long timeRegistered = entry.getValue();
89                 pw.print("  ");
90                 pw.print(sc.getName());
91                 pw.print(" (" + sc.getCallsite() + ")");
92                 pw.println(" [" + ((now - timeRegistered) / 1000) + "s ago]");
93             }
94         }
95     }
96 
97     // The threshold at which to dump information about all the known active SurfaceControls in the
98     // process when the number of layers exceeds a certain count.  This should be significantly
99     // smaller than the MAX_LAYERS (currently 4096) defined in SurfaceFlinger.h
100     private static final int MAX_LAYERS_REPORTING_THRESHOLD = 1024;
101 
102     // The threshold at which to reset the dump state.  Needs to be smaller than
103     // MAX_LAYERS_REPORTING_THRESHOLD
104     private static final int RESET_REPORTING_THRESHOLD = 256;
105 
106     // Number of surface controls to dump when the max threshold is exceeded
107     private static final int DUMP_LIMIT = 256;
108 
109     // An instance of a registry that is a no-op
110     private static final SurfaceControlRegistry NO_OP_REGISTRY = new NoOpRegistry();
111 
112     // Static lock, must be held for all registry operations
113     private static final Object sLock = new Object();
114 
115     // The default reporter for printing out the registered surfaces
116     private static final DefaultReporter sDefaultReporter = new DefaultReporter();
117 
118     // The registry for a given process
119     private static volatile SurfaceControlRegistry sProcessRegistry;
120 
121     // Whether call stack debugging has been initialized. This is evaluated only once per process
122     // instance when the first SurfaceControl.Transaction object is created
123     static boolean sCallStackDebuggingInitialized;
124 
125     // Whether call stack debugging is currently enabled, ie. whether there is a valid match string
126     // for either a specific surface control name or surface control transaction method
127     static boolean sCallStackDebuggingEnabled;
128 
129     // The name of the surface control to log stack traces for.  Always non-null if
130     // sCallStackDebuggingEnabled is true.  Can be combined with the match call.
131     private static String sCallStackDebuggingMatchName;
132 
133     // The surface control transaction method name to log stack traces for.  Always non-null if
134     // sCallStackDebuggingEnabled is true.  Can be combined with the match name.
135     private static String sCallStackDebuggingMatchCall;
136 
137     // Mapping of the active SurfaceControls to the elapsed time when they were registered
138     @GuardedBy("sLock")
139     private final WeakHashMap<SurfaceControl, Long> mSurfaceControls;
140 
141     // The threshold at which we dump information about the current set of registered surfaces.
142     // Once this threshold is reached, we no longer report until the number of layers drops below
143     // mResetReportingThreshold to ensure that we don't spam logcat.
144     private int mMaxLayersReportingThreshold = MAX_LAYERS_REPORTING_THRESHOLD;
145     private int mResetReportingThreshold = RESET_REPORTING_THRESHOLD;
146 
147     // Whether the current set of layers has exceeded mMaxLayersReportingThreshold, and we have
148     // already reported the set of registered surfaces.
149     private boolean mHasReportedExceedingMaxThreshold = false;
150 
151     // The handler for when the registry exceeds the max threshold
152     private Reporter mReporter = sDefaultReporter;
153 
SurfaceControlRegistry()154     private SurfaceControlRegistry() {
155         mSurfaceControls = new WeakHashMap<>(256);
156     }
157 
158     /**
159      * Sets the thresholds at which the registry reports errors.
160      * @param maxLayersReportingThreshold The max threshold (inclusive)
161      * @param resetReportingThreshold The reset threshold (inclusive)
162      * @hide
163      */
164     @VisibleForTesting
setReportingThresholds(int maxLayersReportingThreshold, int resetReportingThreshold, Reporter reporter)165     public void setReportingThresholds(int maxLayersReportingThreshold, int resetReportingThreshold,
166             Reporter reporter) {
167         synchronized (sLock) {
168             if (maxLayersReportingThreshold <= 0
169                     || resetReportingThreshold >= maxLayersReportingThreshold) {
170                 throw new IllegalArgumentException("Expected maxLayersReportingThreshold ("
171                         + maxLayersReportingThreshold + ") to be > 0 and resetReportingThreshold ("
172                         + resetReportingThreshold + ") to be < maxLayersReportingThreshold");
173             }
174             if (reporter == null) {
175                 throw new IllegalArgumentException("Expected non-null reporter");
176             }
177             mMaxLayersReportingThreshold = maxLayersReportingThreshold;
178             mResetReportingThreshold = resetReportingThreshold;
179             mHasReportedExceedingMaxThreshold = false;
180             mReporter = reporter;
181         }
182     }
183 
184     @VisibleForTesting
setCallStackDebuggingParams(String matchName, String matchCall)185     public void setCallStackDebuggingParams(String matchName, String matchCall) {
186         sCallStackDebuggingMatchName = matchName.toLowerCase();
187         sCallStackDebuggingMatchCall = matchCall.toLowerCase();
188     }
189 
190     /**
191      * Creates and initializes the registry for all SurfaceControls in this process. The caller must
192      * hold the READ_FRAME_BUFFER permission.
193      * @hide
194      */
195     @RequiresPermission(READ_FRAME_BUFFER)
196     @NonNull
createProcessInstance(Context context)197     public static void createProcessInstance(Context context) {
198         if (context.checkSelfPermission(READ_FRAME_BUFFER) != PERMISSION_GRANTED) {
199             throw new SecurityException("Expected caller to hold READ_FRAME_BUFFER");
200         }
201         synchronized (sLock) {
202             if (sProcessRegistry == null) {
203                 sProcessRegistry = new SurfaceControlRegistry();
204             }
205         }
206     }
207 
208     /**
209      * Destroys the previously created registry this process.
210      * @hide
211      */
destroyProcessInstance()212     public static void destroyProcessInstance() {
213         synchronized (sLock) {
214             if (sProcessRegistry == null) {
215                 return;
216             }
217             sProcessRegistry = null;
218         }
219     }
220 
221     /**
222      * Returns the instance of the registry for this process, only non-null if
223      * createProcessInstance(Context) was previously called from a valid caller.
224      * @hide
225      */
getProcessInstance()226     public static SurfaceControlRegistry getProcessInstance() {
227         synchronized (sLock) {
228             return sProcessRegistry != null ? sProcessRegistry : NO_OP_REGISTRY;
229         }
230     }
231 
232     /**
233      * Adds a SurfaceControl to the registry.
234      */
add(SurfaceControl sc)235     void add(SurfaceControl sc) {
236         synchronized (sLock) {
237             mSurfaceControls.put(sc, SystemClock.elapsedRealtime());
238             if (!mHasReportedExceedingMaxThreshold
239                     && mSurfaceControls.size() >= mMaxLayersReportingThreshold) {
240                 // Dump existing info to logcat for debugging purposes (but don't close the
241                 // System.out output stream otherwise we can't print to it after this call)
242                 PrintWriter pw = new PrintWriter(System.out, true /* autoFlush */);
243                 mReporter.onMaxLayersExceeded(mSurfaceControls, DUMP_LIMIT, pw);
244                 mHasReportedExceedingMaxThreshold = true;
245             }
246         }
247     }
248 
249     /**
250      * Removes a SurfaceControl from the registry.
251      */
remove(SurfaceControl sc)252     void remove(SurfaceControl sc) {
253         synchronized (sLock) {
254             mSurfaceControls.remove(sc);
255             if (mHasReportedExceedingMaxThreshold
256                     && mSurfaceControls.size() <= mResetReportingThreshold) {
257                 mHasReportedExceedingMaxThreshold = false;
258             }
259         }
260     }
261 
262     /**
263      * Returns a hash of this registry and is a function of all the active surface controls. This
264      * is useful for testing to determine whether the registry has changed between creating and
265      * destroying new SurfaceControls.
266      */
267     @Override
hashCode()268     public int hashCode() {
269         synchronized (sLock) {
270             // Return a hash of the surface controls
271             return mSurfaceControls.keySet().hashCode();
272         }
273     }
274 
275     /**
276      * Initializes global call stack debugging if this is a debug build and a filter is specified.
277      * This is a no-op if
278      *
279      * Usage:
280      *   adb shell setprop persist.wm.debug.sc.tx.log_match_call <call or \"\" to unset>
281      *   adb shell setprop persist.wm.debug.sc.tx.log_match_name <name or \"\" to unset>
282      *   adb reboot
283      */
initializeCallStackDebugging()284     final static void initializeCallStackDebugging() {
285         if (sCallStackDebuggingInitialized || !Build.IS_DEBUGGABLE) {
286             // Return early if already initialized or this is not a debug build
287             return;
288         }
289 
290         sCallStackDebuggingInitialized = true;
291         sCallStackDebuggingMatchCall =
292                 SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null)
293                         .toLowerCase();
294         sCallStackDebuggingMatchName =
295                 SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null)
296                         .toLowerCase();
297         // Only enable stack debugging if any of the match filters are set
298         sCallStackDebuggingEnabled = (!sCallStackDebuggingMatchCall.isEmpty()
299                 || !sCallStackDebuggingMatchName.isEmpty());
300         if (sCallStackDebuggingEnabled) {
301             Log.d(TAG, "Enabling transaction call stack debugging:"
302                     + " matchCall=" + sCallStackDebuggingMatchCall
303                     + " matchName=" + sCallStackDebuggingMatchName);
304         }
305     }
306 
307     /**
308      * Dumps the callstack if it matches the global debug properties. Caller should first verify
309      * {@link #sCallStackDebuggingEnabled} is true.
310      *
311      * @param call the name of the call
312      * @param tx (optional) the transaction associated with this call
313      * @param sc the affected surface
314      * @param details additional details to print with the stack track
315      */
checkCallStackDebugging(@onNull String call, @Nullable SurfaceControl.Transaction tx, @Nullable SurfaceControl sc, @Nullable String details)316     final void checkCallStackDebugging(@NonNull String call,
317             @Nullable SurfaceControl.Transaction tx, @Nullable SurfaceControl sc,
318             @Nullable String details) {
319         if (!sCallStackDebuggingEnabled) {
320             return;
321         }
322         if (!matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) {
323             return;
324         }
325         final String txMsg = tx != null ? "tx=" + tx.getId() + " ": "";
326         final String scMsg = sc != null ? " sc=" + sc.getName() + "": "";
327         final String msg = details != null
328                 ? call + " (" + txMsg + scMsg + ") " + details
329                 : call + " (" + txMsg + scMsg + ")";
330         Log.e(TAG, msg, new Throwable());
331     }
332 
333     /**
334      * Tests whether the given surface control name/method call matches the filters set for the
335      * call stack debugging.
336      */
337     @VisibleForTesting
matchesForCallStackDebugging(@ullable String name, @NonNull String call)338     public final boolean matchesForCallStackDebugging(@Nullable String name, @NonNull String call) {
339         final boolean matchCall = !sCallStackDebuggingMatchCall.isEmpty();
340         if (matchCall && !sCallStackDebuggingMatchCall.contains(call.toLowerCase())) {
341             // Skip if target call doesn't match requested caller
342             return false;
343         }
344         final boolean matchName = !sCallStackDebuggingMatchName.isEmpty();
345         if (!matchName) {
346             return true;
347         }
348         if (name == null) {
349             return false;
350         }
351         return sCallStackDebuggingMatchName.contains(name.toLowerCase()) ||
352                         name.toLowerCase().contains(sCallStackDebuggingMatchName);
353     }
354 
355     /**
356      * Returns whether call stack debugging is enabled for this process.
357      */
isCallStackDebuggingEnabled()358     final static boolean isCallStackDebuggingEnabled() {
359         return sCallStackDebuggingEnabled;
360     }
361 
362     /**
363      * Forces the gc and finalizers to run, used prior to dumping to ensure we only dump strongly
364      * referenced surface controls.
365      */
runGcAndFinalizers()366     private static void runGcAndFinalizers() {
367         long t = SystemClock.elapsedRealtime();
368         GcUtils.runGcAndFinalizersSync();
369         Log.i(TAG, "Ran gc and finalizers (" + (SystemClock.elapsedRealtime() - t) + "ms)");
370     }
371 
372     /**
373      * Dumps information about the set of SurfaceControls in the registry.
374      *
375      * @param limit the number of layers to report
376      * @param runGc whether to run the GC and finalizers before dumping
377      * @hide
378      */
dump(int limit, boolean runGc, PrintWriter pw)379     public static void dump(int limit, boolean runGc, PrintWriter pw) {
380         if (runGc) {
381             // This needs to run outside the lock since finalization isn't synchronous
382             runGcAndFinalizers();
383         }
384         synchronized (sLock) {
385             if (sProcessRegistry != null) {
386                 sDefaultReporter.onMaxLayersExceeded(sProcessRegistry.mSurfaceControls, limit, pw);
387                 pw.println("sCallStackDebuggingInitialized=" + sCallStackDebuggingInitialized);
388                 pw.println("sCallStackDebuggingEnabled=" + sCallStackDebuggingEnabled);
389                 pw.println("sCallStackDebuggingMatchName=" + sCallStackDebuggingMatchName);
390                 pw.println("sCallStackDebuggingMatchCall=" + sCallStackDebuggingMatchCall);
391             }
392         }
393     }
394 
395     /**
396      * A no-op implementation of the registry.
397      */
398     private static class NoOpRegistry extends SurfaceControlRegistry {
399 
400         @Override
setReportingThresholds(int maxLayersReportingThreshold, int resetReportingThreshold, Reporter reporter)401         public void setReportingThresholds(int maxLayersReportingThreshold,
402                 int resetReportingThreshold, Reporter reporter) {}
403 
404         @Override
add(SurfaceControl sc)405         void add(SurfaceControl sc) {}
406 
407         @Override
remove(SurfaceControl sc)408         void remove(SurfaceControl sc) {}
409     }
410 }
411