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