• 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.internal.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         if (IS_USER) {
118             logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
119             return;
120         }
121         synchronized (mEnabledLock) {
122             logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
123             mEnabled = mEnabledLockFree = false;
124 
125             if (mEnabled) {
126                 logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
127                 throw new IllegalStateException("tracing enabled while waiting for flush.");
128             }
129             writeTraceToFileLocked();
130             logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
131         }
132         ProtoLogImpl.getSingleInstance().stopProtoLog(pw, true);
133     }
134 
135     /**
136      * Stops the trace and write the current buffer to disk then restart, if it's already running.
137      * @param pw Print writer
138      */
saveForBugreport(@ullable PrintWriter pw)139     void saveForBugreport(@Nullable PrintWriter pw) {
140         if (IS_USER) {
141             logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
142             return;
143         }
144         synchronized (mEnabledLock) {
145             if (!mEnabled) {
146                 return;
147             }
148             mEnabled = mEnabledLockFree = false;
149             logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
150             writeTraceToFileLocked();
151             logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
152             ProtoLogImpl.getSingleInstance().stopProtoLog(pw, true);
153             logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
154             mBuffer.resetBuffer();
155             mEnabled = mEnabledLockFree = true;
156             ProtoLogImpl.getSingleInstance().startProtoLog(pw);
157         }
158     }
159 
setLogLevel(@indowTraceLogLevel int logLevel, PrintWriter pw)160     private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
161         logAndPrintln(pw, "Setting window tracing log level to " + logLevel);
162         mLogLevel = logLevel;
163 
164         switch (logLevel) {
165             case WindowTraceLogLevel.ALL: {
166                 setBufferCapacity(BUFFER_CAPACITY_ALL, pw);
167                 break;
168             }
169             case WindowTraceLogLevel.TRIM: {
170                 setBufferCapacity(BUFFER_CAPACITY_TRIM, pw);
171                 break;
172             }
173             case WindowTraceLogLevel.CRITICAL: {
174                 setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw);
175                 break;
176             }
177         }
178     }
179 
setLogFrequency(boolean onFrame, PrintWriter pw)180     private void setLogFrequency(boolean onFrame, PrintWriter pw) {
181         logAndPrintln(pw, "Setting window tracing log frequency to "
182                 + ((onFrame) ? "frame" : "transaction"));
183         mLogOnFrame = onFrame;
184     }
185 
setBufferCapacity(int capacity, PrintWriter pw)186     private void setBufferCapacity(int capacity, PrintWriter pw) {
187         logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes");
188         mBuffer.setCapacity(capacity);
189     }
190 
isEnabled()191     boolean isEnabled() {
192         return mEnabledLockFree;
193     }
194 
onShellCommand(ShellCommand shell)195     int onShellCommand(ShellCommand shell) {
196         PrintWriter pw = shell.getOutPrintWriter();
197         String cmd = shell.getNextArgRequired();
198         switch (cmd) {
199             case "start":
200                 startTrace(pw);
201                 return 0;
202             case "stop":
203                 stopTrace(pw);
204                 return 0;
205             case "save-for-bugreport":
206                 saveForBugreport(pw);
207                 return 0;
208             case "status":
209                 logAndPrintln(pw, getStatus());
210                 return 0;
211             case "frame":
212                 setLogFrequency(true /* onFrame */, pw);
213                 mBuffer.resetBuffer();
214                 return 0;
215             case "transaction":
216                 setLogFrequency(false /* onFrame */, pw);
217                 mBuffer.resetBuffer();
218                 return 0;
219             case "level":
220                 String logLevelStr = shell.getNextArgRequired().toLowerCase();
221                 switch (logLevelStr) {
222                     case "all": {
223                         setLogLevel(WindowTraceLogLevel.ALL, pw);
224                         break;
225                     }
226                     case "trim": {
227                         setLogLevel(WindowTraceLogLevel.TRIM, pw);
228                         break;
229                     }
230                     case "critical": {
231                         setLogLevel(WindowTraceLogLevel.CRITICAL, pw);
232                         break;
233                     }
234                     default: {
235                         setLogLevel(WindowTraceLogLevel.TRIM, pw);
236                         break;
237                     }
238                 }
239                 mBuffer.resetBuffer();
240                 return 0;
241             case "size":
242                 setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw);
243                 mBuffer.resetBuffer();
244                 return 0;
245             default:
246                 pw.println("Unknown command: " + cmd);
247                 pw.println("Window manager trace options:");
248                 pw.println("  start: Start logging");
249                 pw.println("  stop: Stop logging");
250                 pw.println("  save-for-bugreport: Save logging data to file if it's running.");
251                 pw.println("  frame: Log trace once per frame");
252                 pw.println("  transaction: Log each transaction");
253                 pw.println("  size: Set the maximum log size (in KB)");
254                 pw.println("  status: Print trace status");
255                 pw.println("  level [lvl]: Set the log level between");
256                 pw.println("    lvl may be one of:");
257                 pw.println("      critical: Only visible windows with reduced information");
258                 pw.println("      trim: All windows with reduced");
259                 pw.println("      all: All window and information");
260                 return -1;
261         }
262     }
263 
getStatus()264     String getStatus() {
265         return "Status: "
266                 + ((isEnabled()) ? "Enabled" : "Disabled")
267                 + "\n"
268                 + "Log level: "
269                 + mLogLevel
270                 + "\n"
271                 + mBuffer.getStatus();
272     }
273 
274     /**
275      * If tracing is enabled, log the current state or schedule the next frame to be logged,
276      * according to {@link #mLogOnFrame}.
277      *
278      * @param where Logging point descriptor
279      */
logState(String where)280     void logState(String where) {
281         if (!isEnabled()) {
282             return;
283         }
284 
285         if (mLogOnFrame) {
286             schedule();
287         } else {
288             log(where);
289         }
290     }
291 
292     /**
293      * Schedule the log to trace the next frame
294      */
schedule()295     private void schedule() {
296         if (mScheduled) {
297             return;
298         }
299 
300         mScheduled = true;
301         mChoreographer.postFrameCallback(mFrameCallback);
302     }
303 
304     /**
305      * Write the current frame to the buffer
306      *
307      * @param where Logging point descriptor
308      */
log(String where)309     private void log(String where) {
310         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
311         try {
312             ProtoOutputStream os = new ProtoOutputStream();
313             long tokenOuter = os.start(ENTRY);
314             os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
315             os.write(WHERE, where);
316 
317             long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
318             synchronized (mGlobalLock) {
319                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
320                 try {
321                     mService.dumpDebugLocked(os, mLogLevel);
322                 } finally {
323                     Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
324                 }
325             }
326             os.end(tokenInner);
327             os.end(tokenOuter);
328             mBuffer.add(os);
329             mScheduled = false;
330         } catch (Exception e) {
331             Log.wtf(TAG, "Exception while tracing state", e);
332         } finally {
333             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
334         }
335     }
336 
logAndPrintln(@ullable PrintWriter pw, String msg)337     private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
338         Log.i(TAG, msg);
339         if (pw != null) {
340             pw.println(msg);
341             pw.flush();
342         }
343     }
344 
345     /**
346      * Writes the trace buffer to disk. This method has no internal synchronization and should be
347      * externally synchronized
348      */
writeTraceToFileLocked()349     private void writeTraceToFileLocked() {
350         try {
351             Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
352             ProtoOutputStream proto = new ProtoOutputStream();
353             proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
354             mBuffer.writeTraceToFile(mTraceFile, proto);
355         } catch (IOException e) {
356             Log.e(TAG, "Unable to write buffer to file", e);
357         } finally {
358             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
359         }
360     }
361 }
362