blob: ba3dc607f6ccb55d70a813289830ac5030a5702b [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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
17package com.android.server.wm;
18
19import static android.os.Build.IS_USER;
20
21import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
22import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
23import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
24import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
25import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
26import static com.android.server.wm.WindowManagerTraceProto.WHERE;
27import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
28
29import android.annotation.Nullable;
30import android.os.ShellCommand;
31import android.os.SystemClock;
32import android.os.Trace;
33import android.util.Log;
34import android.util.proto.ProtoOutputStream;
35import android.view.Choreographer;
36
37import com.android.server.protolog.ProtoLogImpl;
38import com.android.internal.util.TraceBuffer;
39
40import java.io.File;
41import java.io.IOException;
42import 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 */
48class 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
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
83 private WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
84 int bufferCapacity) {
85 this(file, service, choreographer, service.mGlobalLock, bufferCapacity);
86 }
87
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
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 */
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 */
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
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
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
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
177 boolean isEnabled() {
178 return mEnabledLockFree;
179 }
180
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
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 */
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 */
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 */
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 */
325 void writeTraceToFile() {
326 synchronized (mEnabledLock) {
327 writeTraceToFileLocked();
328 }
329 ProtoLogImpl.getSingleInstance().writeProtoLogToFile();
330 }
331
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 */
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}