• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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