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