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 17 package com.android.server.wm; 18 19 import static android.os.Build.IS_USER; 20 21 import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY; 22 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER; 23 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; 24 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; 25 import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS; 26 import static com.android.server.wm.WindowManagerTraceProto.WHERE; 27 import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE; 28 29 import android.annotation.Nullable; 30 import android.os.ShellCommand; 31 import android.os.SystemClock; 32 import android.os.Trace; 33 import android.util.Log; 34 import android.util.proto.ProtoOutputStream; 35 import android.view.Choreographer; 36 37 import com.android.server.protolog.ProtoLogImpl; 38 import com.android.internal.util.TraceBuffer; 39 40 import java.io.File; 41 import java.io.IOException; 42 import java.io.PrintWriter; 43 44 /** 45 * A class that allows window manager to dump its state continuously to a trace file, such that a 46 * time series of window manager state can be analyzed after the fact. 47 */ 48 class WindowTracing { 49 50 /** 51 * Maximum buffer size, currently defined as 512 KB 52 * Size was experimentally defined to fit between 100 to 150 elements. 53 */ 54 private static final int BUFFER_CAPACITY_CRITICAL = 512 * 1024; 55 private static final int BUFFER_CAPACITY_TRIM = 2048 * 1024; 56 private static final int BUFFER_CAPACITY_ALL = 4096 * 1024; 57 private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace.pb"; 58 private static final String TAG = "WindowTracing"; 59 private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; 60 61 private final WindowManagerService mService; 62 private final Choreographer mChoreographer; 63 private final WindowManagerGlobalLock mGlobalLock; 64 65 private final Object mEnabledLock = new Object(); 66 private final File mTraceFile; 67 private final TraceBuffer mBuffer; 68 private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) -> 69 log("onFrame" /* where */); 70 71 private @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM; 72 private boolean mLogOnFrame = false; 73 private boolean mEnabled; 74 private volatile boolean mEnabledLockFree; 75 private boolean mScheduled; 76 createDefaultAndStartLooper(WindowManagerService service, Choreographer choreographer)77 static WindowTracing createDefaultAndStartLooper(WindowManagerService service, 78 Choreographer choreographer) { 79 File file = new File(TRACE_FILENAME); 80 return new WindowTracing(file, service, choreographer, BUFFER_CAPACITY_TRIM); 81 } 82 WindowTracing(File file, WindowManagerService service, Choreographer choreographer, int bufferCapacity)83 private WindowTracing(File file, WindowManagerService service, Choreographer choreographer, 84 int bufferCapacity) { 85 this(file, service, choreographer, service.mGlobalLock, bufferCapacity); 86 } 87 WindowTracing(File file, WindowManagerService service, Choreographer choreographer, WindowManagerGlobalLock globalLock, int bufferCapacity)88 WindowTracing(File file, WindowManagerService service, Choreographer choreographer, 89 WindowManagerGlobalLock globalLock, int bufferCapacity) { 90 mChoreographer = choreographer; 91 mService = service; 92 mGlobalLock = globalLock; 93 mTraceFile = file; 94 mBuffer = new TraceBuffer(bufferCapacity); 95 setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */); 96 } 97 startTrace(@ullable PrintWriter pw)98 void startTrace(@Nullable PrintWriter pw) { 99 if (IS_USER) { 100 logAndPrintln(pw, "Error: Tracing is not supported on user builds."); 101 return; 102 } 103 synchronized (mEnabledLock) { 104 ProtoLogImpl.getSingleInstance().startProtoLog(pw); 105 logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); 106 mBuffer.resetBuffer(); 107 mEnabled = mEnabledLockFree = true; 108 } 109 log("trace.enable"); 110 } 111 112 /** 113 * Stops the trace and write the current buffer to disk 114 * @param pw Print writer 115 */ stopTrace(@ullable PrintWriter pw)116 void stopTrace(@Nullable PrintWriter pw) { 117 stopTrace(pw, true /* writeToFile */); 118 } 119 120 /** 121 * Stops the trace 122 * @param pw Print writer 123 * @param writeToFile If the current buffer should be written to disk or not 124 */ stopTrace(@ullable PrintWriter pw, boolean writeToFile)125 void stopTrace(@Nullable PrintWriter pw, boolean writeToFile) { 126 if (IS_USER) { 127 logAndPrintln(pw, "Error: Tracing is not supported on user builds."); 128 return; 129 } 130 synchronized (mEnabledLock) { 131 logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush."); 132 mEnabled = mEnabledLockFree = false; 133 134 if (mEnabled) { 135 logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush."); 136 throw new IllegalStateException("tracing enabled while waiting for flush."); 137 } 138 if (writeToFile) { 139 writeTraceToFileLocked(); 140 logAndPrintln(pw, "Trace written to " + mTraceFile + "."); 141 } 142 } 143 ProtoLogImpl.getSingleInstance().stopProtoLog(pw, writeToFile); 144 } 145 setLogLevel(@indowTraceLogLevel int logLevel, PrintWriter pw)146 private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) { 147 logAndPrintln(pw, "Setting window tracing log level to " + logLevel); 148 mLogLevel = logLevel; 149 150 switch (logLevel) { 151 case WindowTraceLogLevel.ALL: { 152 setBufferCapacity(BUFFER_CAPACITY_ALL, pw); 153 break; 154 } 155 case WindowTraceLogLevel.TRIM: { 156 setBufferCapacity(BUFFER_CAPACITY_TRIM, pw); 157 break; 158 } 159 case WindowTraceLogLevel.CRITICAL: { 160 setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw); 161 break; 162 } 163 } 164 } 165 setLogFrequency(boolean onFrame, PrintWriter pw)166 private void setLogFrequency(boolean onFrame, PrintWriter pw) { 167 logAndPrintln(pw, "Setting window tracing log frequency to " 168 + ((onFrame) ? "frame" : "transaction")); 169 mLogOnFrame = onFrame; 170 } 171 setBufferCapacity(int capacity, PrintWriter pw)172 private void setBufferCapacity(int capacity, PrintWriter pw) { 173 logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes"); 174 mBuffer.setCapacity(capacity); 175 } 176 isEnabled()177 boolean isEnabled() { 178 return mEnabledLockFree; 179 } 180 onShellCommand(ShellCommand shell)181 int onShellCommand(ShellCommand shell) { 182 PrintWriter pw = shell.getOutPrintWriter(); 183 String cmd = shell.getNextArgRequired(); 184 switch (cmd) { 185 case "start": 186 startTrace(pw); 187 return 0; 188 case "stop": 189 stopTrace(pw); 190 return 0; 191 case "status": 192 logAndPrintln(pw, getStatus()); 193 return 0; 194 case "frame": 195 setLogFrequency(true /* onFrame */, pw); 196 mBuffer.resetBuffer(); 197 return 0; 198 case "transaction": 199 setLogFrequency(false /* onFrame */, pw); 200 mBuffer.resetBuffer(); 201 return 0; 202 case "level": 203 String logLevelStr = shell.getNextArgRequired().toLowerCase(); 204 switch (logLevelStr) { 205 case "all": { 206 setLogLevel(WindowTraceLogLevel.ALL, pw); 207 break; 208 } 209 case "trim": { 210 setLogLevel(WindowTraceLogLevel.TRIM, pw); 211 break; 212 } 213 case "critical": { 214 setLogLevel(WindowTraceLogLevel.CRITICAL, pw); 215 break; 216 } 217 default: { 218 setLogLevel(WindowTraceLogLevel.TRIM, pw); 219 break; 220 } 221 } 222 mBuffer.resetBuffer(); 223 return 0; 224 case "size": 225 setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw); 226 mBuffer.resetBuffer(); 227 return 0; 228 default: 229 pw.println("Unknown command: " + cmd); 230 pw.println("Window manager trace options:"); 231 pw.println(" start: Start logging"); 232 pw.println(" stop: Stop logging"); 233 pw.println(" frame: Log trace once per frame"); 234 pw.println(" transaction: Log each transaction"); 235 pw.println(" size: Set the maximum log size (in KB)"); 236 pw.println(" status: Print trace status"); 237 pw.println(" level [lvl]: Set the log level between"); 238 pw.println(" lvl may be one of:"); 239 pw.println(" critical: Only visible windows with reduced information"); 240 pw.println(" trim: All windows with reduced"); 241 pw.println(" all: All window and information"); 242 return -1; 243 } 244 } 245 getStatus()246 String getStatus() { 247 return "Status: " 248 + ((isEnabled()) ? "Enabled" : "Disabled") 249 + "\n" 250 + "Log level: " 251 + mLogLevel 252 + "\n" 253 + mBuffer.getStatus(); 254 } 255 256 /** 257 * If tracing is enabled, log the current state or schedule the next frame to be logged, 258 * according to {@link #mLogOnFrame}. 259 * 260 * @param where Logging point descriptor 261 */ logState(String where)262 void logState(String where) { 263 if (!isEnabled()) { 264 return; 265 } 266 267 if (mLogOnFrame) { 268 schedule(); 269 } else { 270 log(where); 271 } 272 } 273 274 /** 275 * Schedule the log to trace the next frame 276 */ schedule()277 private void schedule() { 278 if (mScheduled) { 279 return; 280 } 281 282 mScheduled = true; 283 mChoreographer.postFrameCallback(mFrameCallback); 284 } 285 286 /** 287 * Write the current frame to the buffer 288 * 289 * @param where Logging point descriptor 290 */ log(String where)291 private void log(String where) { 292 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked"); 293 try { 294 ProtoOutputStream os = new ProtoOutputStream(); 295 long tokenOuter = os.start(ENTRY); 296 os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos()); 297 os.write(WHERE, where); 298 299 long tokenInner = os.start(WINDOW_MANAGER_SERVICE); 300 synchronized (mGlobalLock) { 301 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked"); 302 try { 303 mService.dumpDebugLocked(os, mLogLevel); 304 } finally { 305 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); 306 } 307 } 308 os.end(tokenInner); 309 os.end(tokenOuter); 310 mBuffer.add(os); 311 mScheduled = false; 312 } catch (Exception e) { 313 Log.wtf(TAG, "Exception while tracing state", e); 314 } finally { 315 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); 316 } 317 } 318 319 /** 320 * Writes the trace buffer to new file for the bugreport. 321 * 322 * This method is synchronized with {@code #startTrace(PrintWriter)} and 323 * {@link #stopTrace(PrintWriter)}. 324 */ writeTraceToFile()325 void writeTraceToFile() { 326 synchronized (mEnabledLock) { 327 writeTraceToFileLocked(); 328 } 329 ProtoLogImpl.getSingleInstance().writeProtoLogToFile(); 330 } 331 logAndPrintln(@ullable PrintWriter pw, String msg)332 private void logAndPrintln(@Nullable PrintWriter pw, String msg) { 333 Log.i(TAG, msg); 334 if (pw != null) { 335 pw.println(msg); 336 pw.flush(); 337 } 338 } 339 340 /** 341 * Writes the trace buffer to disk. This method has no internal synchronization and should be 342 * externally synchronized 343 */ writeTraceToFileLocked()344 private void writeTraceToFileLocked() { 345 try { 346 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked"); 347 ProtoOutputStream proto = new ProtoOutputStream(); 348 proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); 349 mBuffer.writeTraceToFile(mTraceFile, proto); 350 } catch (IOException e) { 351 Log.e(TAG, "Unable to write buffer to file", e); 352 } finally { 353 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); 354 } 355 } 356 } 357