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