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 import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
21 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
22 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
23 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
24 import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
25 import static com.android.server.wm.WindowManagerTraceProto.WHERE;
26 import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
27 
28 import android.content.Context;
29 import android.os.ShellCommand;
30 import android.os.SystemClock;
31 import android.os.Trace;
32 import android.annotation.Nullable;
33 import android.util.Log;
34 import android.util.proto.ProtoOutputStream;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 
38 import java.io.File;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.OutputStream;
42 import java.io.PrintWriter;
43 import java.util.concurrent.ArrayBlockingQueue;
44 import java.util.concurrent.BlockingQueue;
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     private static final String TAG = "WindowTracing";
53     private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
54 
55     private final Object mLock = new Object();
56     private final File mTraceFile;
57     private final BlockingQueue<ProtoOutputStream> mWriteQueue = new ArrayBlockingQueue<>(200);
58 
59     private boolean mEnabled;
60     private volatile boolean mEnabledLockFree;
61 
WindowTracing(File file)62     WindowTracing(File file) {
63         mTraceFile = file;
64     }
65 
startTrace(@ullable PrintWriter pw)66     void startTrace(@Nullable PrintWriter pw) throws IOException {
67         if (IS_USER){
68             logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
69             return;
70         }
71         synchronized (mLock) {
72             logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
73             mWriteQueue.clear();
74             mTraceFile.delete();
75             try (OutputStream os = new FileOutputStream(mTraceFile)) {
76                 mTraceFile.setReadable(true, false);
77                 ProtoOutputStream proto = new ProtoOutputStream(os);
78                 proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
79                 proto.flush();
80             }
81             mEnabled = mEnabledLockFree = true;
82         }
83     }
84 
logAndPrintln(@ullable PrintWriter pw, String msg)85     private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
86         Log.i(TAG, msg);
87         if (pw != null) {
88             pw.println(msg);
89             pw.flush();
90         }
91     }
92 
stopTrace(@ullable PrintWriter pw)93     void stopTrace(@Nullable PrintWriter pw) {
94         if (IS_USER){
95             logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
96             return;
97         }
98         synchronized (mLock) {
99             logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
100             mEnabled = mEnabledLockFree = false;
101             while (!mWriteQueue.isEmpty()) {
102                 if (mEnabled) {
103                     logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
104                     throw new IllegalStateException("tracing enabled while waiting for flush.");
105                 }
106                 try {
107                     mLock.wait();
108                     mLock.notify();
109                 } catch (InterruptedException e) {
110                     Thread.currentThread().interrupt();
111                 }
112             }
113             logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
114         }
115     }
116 
appendTraceEntry(ProtoOutputStream proto)117     void appendTraceEntry(ProtoOutputStream proto) {
118         if (!mEnabledLockFree) {
119             return;
120         }
121 
122         if (!mWriteQueue.offer(proto)) {
123             Log.e(TAG, "Dropping window trace entry, queue full");
124         }
125     }
126 
loop()127     void loop() {
128         for (;;) {
129             loopOnce();
130         }
131     }
132 
133     @VisibleForTesting
loopOnce()134     void loopOnce() {
135         ProtoOutputStream proto;
136         try {
137             proto = mWriteQueue.take();
138         } catch (InterruptedException e) {
139             Thread.currentThread().interrupt();
140             return;
141         }
142 
143         synchronized (mLock) {
144             try {
145                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
146                 try (OutputStream os = new FileOutputStream(mTraceFile, true /* append */)) {
147                     os.write(proto.getBytes());
148                 }
149             } catch (IOException e) {
150                 Log.e(TAG, "Failed to write file " + mTraceFile, e);
151             } finally {
152                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
153             }
154             mLock.notify();
155         }
156     }
157 
isEnabled()158     boolean isEnabled() {
159         return mEnabledLockFree;
160     }
161 
createDefaultAndStartLooper(Context context)162     static WindowTracing createDefaultAndStartLooper(Context context) {
163         File file = new File("/data/misc/wmtrace/wm_trace.pb");
164         WindowTracing windowTracing = new WindowTracing(file);
165         if (!IS_USER){
166             new Thread(windowTracing::loop, "window_tracing").start();
167         }
168         return windowTracing;
169     }
170 
onShellCommand(ShellCommand shell, String cmd)171     int onShellCommand(ShellCommand shell, String cmd) {
172         PrintWriter pw = shell.getOutPrintWriter();
173         try {
174             switch (cmd) {
175                 case "start":
176                     startTrace(pw);
177                     return 0;
178                 case "stop":
179                     stopTrace(pw);
180                     return 0;
181                 default:
182                     pw.println("Unknown command: " + cmd);
183                     return -1;
184             }
185         } catch (IOException e) {
186             logAndPrintln(pw, e.toString());
187             throw new RuntimeException(e);
188         }
189     }
190 
traceStateLocked(String where, WindowManagerService service)191     void traceStateLocked(String where, WindowManagerService service) {
192         if (!isEnabled()) {
193             return;
194         }
195         ProtoOutputStream os = new ProtoOutputStream();
196         long tokenOuter = os.start(ENTRY);
197         os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
198         os.write(WHERE, where);
199 
200         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
201         try {
202             long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
203             service.writeToProtoLocked(os, true /* trim */);
204             os.end(tokenInner);
205         } finally {
206             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
207         }
208         os.end(tokenOuter);
209         appendTraceEntry(os);
210         Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
211     }
212 }
213