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