1 /* 2 * Copyright (C) 2022 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.shell.ChangeInfo.CHANGE_FLAGS; 22 import static com.android.server.wm.shell.ChangeInfo.HAS_CHANGED; 23 import static com.android.server.wm.shell.ChangeInfo.TRANSIT_MODE; 24 import static com.android.server.wm.shell.ChangeInfo.WINDOW_IDENTIFIER; 25 import static com.android.server.wm.shell.Transition.CHANGE; 26 import static com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID; 27 import static com.android.server.wm.shell.Transition.FLAGS; 28 import static com.android.server.wm.shell.Transition.ID; 29 import static com.android.server.wm.shell.Transition.START_TRANSACTION_ID; 30 import static com.android.server.wm.shell.Transition.STATE; 31 import static com.android.server.wm.shell.Transition.TIMESTAMP; 32 import static com.android.server.wm.shell.Transition.TRANSITION_TYPE; 33 import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER; 34 import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_H; 35 import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_L; 36 import static com.android.server.wm.shell.TransitionTraceProto.TRANSITION; 37 38 import android.annotation.NonNull; 39 import android.annotation.Nullable; 40 import android.os.SystemClock; 41 import android.os.Trace; 42 import android.util.Log; 43 import android.util.proto.ProtoOutputStream; 44 45 import com.android.internal.util.TraceBuffer; 46 import com.android.server.wm.Transition.ChangeInfo; 47 import com.android.server.wm.shell.TransitionTraceProto; 48 49 import java.io.File; 50 import java.io.IOException; 51 import java.io.PrintWriter; 52 53 /** 54 * Helper class to collect and dump transition traces. 55 */ 56 public class TransitionTracer { 57 58 private static final String LOG_TAG = "TransitionTracer"; 59 60 /** 61 * Maximum buffer size, currently defined as 5 MB 62 */ 63 private static final int BUFFER_CAPACITY = 5120 * 1024; // 5 MB 64 static final String WINSCOPE_EXT = ".winscope"; 65 private static final String TRACE_FILE = "/data/misc/wmtrace/transition_trace" + WINSCOPE_EXT; 66 private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; 67 68 private final TransitionTraceBuffer mTraceBuffer = new TransitionTraceBuffer(); 69 70 private final Object mEnabledLock = new Object(); 71 private volatile boolean mEnabled = false; 72 73 private long mTraceStartTimestamp; 74 75 private class TransitionTraceBuffer { 76 private final TraceBuffer mBuffer = new TraceBuffer(BUFFER_CAPACITY); 77 pushTransitionState(Transition transition)78 private void pushTransitionState(Transition transition) { 79 final ProtoOutputStream outputStream = new ProtoOutputStream(); 80 final long transitionEntryToken = outputStream.start(TRANSITION); 81 82 outputStream.write(ID, transition.getSyncId()); 83 outputStream.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos()); 84 outputStream.write(TRANSITION_TYPE, transition.mType); 85 outputStream.write(STATE, transition.getState()); 86 outputStream.write(FLAGS, transition.getFlags()); 87 if (transition.getStartTransaction() != null) { 88 outputStream.write(START_TRANSACTION_ID, transition.getStartTransaction().getId()); 89 } 90 if (transition.getFinishTransaction() != null) { 91 outputStream.write(FINISH_TRANSACTION_ID, 92 transition.getFinishTransaction().getId()); 93 } 94 95 for (int i = 0; i < transition.mChanges.size(); ++i) { 96 final WindowContainer window = transition.mChanges.keyAt(i); 97 final ChangeInfo changeInfo = transition.mChanges.valueAt(i); 98 writeChange(outputStream, window, changeInfo); 99 } 100 101 outputStream.end(transitionEntryToken); 102 103 mBuffer.add(outputStream); 104 } 105 writeChange(ProtoOutputStream outputStream, WindowContainer window, ChangeInfo changeInfo)106 private void writeChange(ProtoOutputStream outputStream, WindowContainer window, 107 ChangeInfo changeInfo) { 108 Trace.beginSection("TransitionProto#addChange"); 109 final long changeEntryToken = outputStream.start(CHANGE); 110 111 final int transitMode = changeInfo.getTransitMode(window); 112 final boolean hasChanged = changeInfo.hasChanged(window); 113 final int changeFlags = changeInfo.getChangeFlags(window); 114 115 outputStream.write(TRANSIT_MODE, transitMode); 116 outputStream.write(HAS_CHANGED, hasChanged); 117 outputStream.write(CHANGE_FLAGS, changeFlags); 118 window.writeIdentifierToProto(outputStream, WINDOW_IDENTIFIER); 119 120 outputStream.end(changeEntryToken); 121 Trace.endSection(); 122 } 123 writeToFile(File file, ProtoOutputStream proto)124 public void writeToFile(File file, ProtoOutputStream proto) throws IOException { 125 mBuffer.writeTraceToFile(file, proto); 126 } 127 reset()128 public void reset() { 129 mBuffer.resetBuffer(); 130 } 131 } 132 133 /** 134 * Records the current state of a transition in the transition trace (if it is running). 135 * @param transition the transition that we want to record the state of. 136 */ logState(com.android.server.wm.Transition transition)137 public void logState(com.android.server.wm.Transition transition) { 138 if (!mEnabled) { 139 return; 140 } 141 142 Log.d(LOG_TAG, "Logging state of transition " + transition); 143 mTraceBuffer.pushTransitionState(transition); 144 } 145 146 /** 147 * Starts collecting transitions for the trace. 148 * If called while a trace is already running, this will reset the trace. 149 */ startTrace(@ullable PrintWriter pw)150 public void startTrace(@Nullable PrintWriter pw) { 151 if (IS_USER) { 152 LogAndPrintln.e(pw, "Tracing is not supported on user builds."); 153 return; 154 } 155 Trace.beginSection("TransitionTracer#startTrace"); 156 LogAndPrintln.i(pw, "Starting shell transition trace."); 157 synchronized (mEnabledLock) { 158 mTraceStartTimestamp = SystemClock.elapsedRealtime(); 159 mEnabled = true; 160 mTraceBuffer.reset(); 161 } 162 Trace.endSection(); 163 } 164 165 /** 166 * Stops collecting the transition trace and dump to trace to file. 167 * 168 * Dumps the trace to @link{TRACE_FILE}. 169 */ stopTrace(@ullable PrintWriter pw)170 public void stopTrace(@Nullable PrintWriter pw) { 171 stopTrace(pw, new File(TRACE_FILE)); 172 } 173 174 /** 175 * Stops collecting the transition trace and dump to trace to file. 176 * @param outputFile The file to dump the transition trace to. 177 */ stopTrace(@ullable PrintWriter pw, File outputFile)178 public void stopTrace(@Nullable PrintWriter pw, File outputFile) { 179 if (IS_USER) { 180 LogAndPrintln.e(pw, "Tracing is not supported on user builds."); 181 return; 182 } 183 Trace.beginSection("TransitionTracer#stopTrace"); 184 LogAndPrintln.i(pw, "Stopping shell transition trace."); 185 synchronized (mEnabledLock) { 186 if (!mEnabled) { 187 LogAndPrintln.e(pw, 188 "Error: Tracing can't be stopped because it hasn't been started."); 189 return; 190 } 191 192 mEnabled = false; 193 writeTraceToFileLocked(pw, outputFile); 194 } 195 Trace.endSection(); 196 } 197 isEnabled()198 boolean isEnabled() { 199 return mEnabled; 200 } 201 writeTraceToFileLocked(@ullable PrintWriter pw, File file)202 private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) { 203 Trace.beginSection("TransitionTracer#writeTraceToFileLocked"); 204 try { 205 ProtoOutputStream proto = new ProtoOutputStream(); 206 proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); 207 proto.write(TransitionTraceProto.TIMESTAMP, mTraceStartTimestamp); 208 int pid = android.os.Process.myPid(); 209 LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath() 210 + " from process " + pid); 211 mTraceBuffer.writeToFile(file, proto); 212 } catch (IOException e) { 213 LogAndPrintln.e(pw, "Unable to write buffer to file", e); 214 } 215 Trace.endSection(); 216 } 217 218 private static class LogAndPrintln { i(@ullable PrintWriter pw, String msg)219 private static void i(@Nullable PrintWriter pw, String msg) { 220 Log.i(LOG_TAG, msg); 221 if (pw != null) { 222 pw.println(msg); 223 pw.flush(); 224 } 225 } 226 e(@ullable PrintWriter pw, String msg)227 private static void e(@Nullable PrintWriter pw, String msg) { 228 Log.e(LOG_TAG, msg); 229 if (pw != null) { 230 pw.println("ERROR: " + msg); 231 pw.flush(); 232 } 233 } 234 e(@ullable PrintWriter pw, String msg, @NonNull Exception e)235 private static void e(@Nullable PrintWriter pw, String msg, @NonNull Exception e) { 236 Log.e(LOG_TAG, msg, e); 237 if (pw != null) { 238 pw.println("ERROR: " + msg + " ::\n " + e); 239 pw.flush(); 240 } 241 } 242 } 243 } 244