• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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