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