blob: c59d2d392e93f6f47e3d5710bc7d1c586903313e [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm;
import static android.os.Build.IS_USER;
import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER;
import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_H;
import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_L;
import static com.android.server.wm.shell.TransitionTraceProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.os.Trace;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.TraceBuffer;
import com.android.server.wm.Transition.ChangeInfo;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
/**
* Helper class to collect and dump transition traces.
*/
public class TransitionTracer {
private static final String LOG_TAG = "TransitionTracer";
private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
// This will be the size the proto output streams are initialized to.
// Ideally this should fit most or all the proto objects we will create and be no bigger than
// that to ensure to don't use excessive amounts of memory.
private static final int CHUNK_SIZE = 64;
static final String WINSCOPE_EXT = ".winscope";
private static final String TRACE_FILE =
"/data/misc/wmtrace/wm_transition_trace" + WINSCOPE_EXT;
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
private final TraceBuffer mTraceBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY);
private final Object mEnabledLock = new Object();
private volatile boolean mActiveTracingEnabled = false;
/**
* Records key information about a transition that has been sent to Shell to be played.
* More information will be appended to the same proto object once the transition is finished or
* aborted.
* Transition information won't be added to the trace buffer until
* {@link #logFinishedTransition} or {@link #logAbortedTransition} is called for this
* transition.
*
* @param transition The transition that has been sent to Shell.
* @param targets Information about the target windows of the transition.
*/
public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets) {
try {
final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
final long protoToken = outputStream
.start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS,
transition.mLogger.mCreateTimeNs);
outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS,
transition.mLogger.mSendTimeNs);
outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID,
transition.getStartTransaction().getId());
outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID,
transition.getFinishTransaction().getId());
dumpTransitionTargetsToProto(outputStream, transition, targets);
outputStream.end(protoToken);
mTraceBuffer.add(outputStream);
} catch (Exception e) {
// Don't let any errors in the tracing cause the transition to fail
Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
}
}
/**
* Completes the information dumped in {@link #logSentTransition} for a transition
* that has finished or aborted, and add the proto object to the trace buffer.
*
* @param transition The transition that has finished.
*/
public void logFinishedTransition(Transition transition) {
try {
final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
final long protoToken = outputStream
.start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS,
transition.mLogger.mFinishTimeNs);
outputStream.end(protoToken);
mTraceBuffer.add(outputStream);
} catch (Exception e) {
// Don't let any errors in the tracing cause the transition to fail
Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
}
}
/**
* Same as {@link #logFinishedTransition} but don't add the transition to the trace buffer
* unless actively tracing.
*
* @param transition The transition that has been aborted
*/
public void logAbortedTransition(Transition transition) {
try {
final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
final long protoToken = outputStream
.start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
outputStream.write(com.android.server.wm.shell.Transition.ABORT_TIME_NS,
transition.mLogger.mAbortTimeNs);
outputStream.end(protoToken);
mTraceBuffer.add(outputStream);
} catch (Exception e) {
// Don't let any errors in the tracing cause the transition to fail
Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
}
}
void logRemovingStartingWindow(@NonNull StartingData startingData) {
if (startingData.mTransitionId == 0) {
return;
}
try {
final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
final long protoToken = outputStream
.start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
outputStream.write(com.android.server.wm.shell.Transition.ID,
startingData.mTransitionId);
outputStream.write(
com.android.server.wm.shell.Transition.STARTING_WINDOW_REMOVE_TIME_NS,
SystemClock.elapsedRealtimeNanos());
outputStream.end(protoToken);
mTraceBuffer.add(outputStream);
} catch (Exception e) {
Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
}
}
private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
Transition transition, ArrayList<ChangeInfo> targets) {
Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto");
if (mActiveTracingEnabled) {
outputStream.write(com.android.server.wm.shell.Transition.ID,
transition.getSyncId());
}
outputStream.write(com.android.server.wm.shell.Transition.TYPE, transition.mType);
outputStream.write(com.android.server.wm.shell.Transition.FLAGS, transition.getFlags());
for (int i = 0; i < targets.size(); ++i) {
final long changeToken = outputStream
.start(com.android.server.wm.shell.Transition.TARGETS);
final Transition.ChangeInfo target = targets.get(i);
final int layerId;
if (target.mContainer.mSurfaceControl.isValid()) {
layerId = target.mContainer.mSurfaceControl.getLayerId();
} else {
layerId = -1;
}
outputStream.write(com.android.server.wm.shell.Target.MODE, target.mReadyMode);
outputStream.write(com.android.server.wm.shell.Target.FLAGS, target.mReadyFlags);
outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId);
if (mActiveTracingEnabled) {
// What we use in the WM trace
final int windowId = System.identityHashCode(target.mContainer);
outputStream.write(com.android.server.wm.shell.Target.WINDOW_ID, windowId);
}
outputStream.end(changeToken);
}
Trace.endSection();
}
/**
* Starts collecting transitions for the trace.
* If called while a trace is already running, this will reset the trace.
*/
public void startTrace(@Nullable PrintWriter pw) {
if (IS_USER) {
LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
return;
}
Trace.beginSection("TransitionTracer#startTrace");
LogAndPrintln.i(pw, "Starting shell transition trace.");
synchronized (mEnabledLock) {
mActiveTracingEnabled = true;
mTraceBuffer.resetBuffer();
mTraceBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
}
Trace.endSection();
}
/**
* Stops collecting the transition trace and dump to trace to file.
*
* Dumps the trace to @link{TRACE_FILE}.
*/
public void stopTrace(@Nullable PrintWriter pw) {
stopTrace(pw, new File(TRACE_FILE));
}
/**
* Stops collecting the transition trace and dump to trace to file.
* @param outputFile The file to dump the transition trace to.
*/
public void stopTrace(@Nullable PrintWriter pw, File outputFile) {
if (IS_USER) {
LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
return;
}
Trace.beginSection("TransitionTracer#stopTrace");
LogAndPrintln.i(pw, "Stopping shell transition trace.");
synchronized (mEnabledLock) {
mActiveTracingEnabled = false;
writeTraceToFileLocked(pw, outputFile);
mTraceBuffer.resetBuffer();
mTraceBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
}
Trace.endSection();
}
/**
* Being called while taking a bugreport so that tracing files can be included in the bugreport.
*
* @param pw Print writer
*/
public void saveForBugreport(@Nullable PrintWriter pw) {
if (IS_USER) {
LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
return;
}
Trace.beginSection("TransitionTracer#saveForBugreport");
synchronized (mEnabledLock) {
final File outputFile = new File(TRACE_FILE);
writeTraceToFileLocked(pw, outputFile);
}
Trace.endSection();
}
boolean isActiveTracingEnabled() {
return mActiveTracingEnabled;
}
private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
try {
ProtoOutputStream proto = new ProtoOutputStream(CHUNK_SIZE);
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
long timeOffsetNs =
TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
- SystemClock.elapsedRealtimeNanos();
proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
int pid = android.os.Process.myPid();
LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
+ " from process " + pid);
mTraceBuffer.writeTraceToFile(file, proto);
} catch (IOException e) {
LogAndPrintln.e(pw, "Unable to write buffer to file", e);
}
Trace.endSection();
}
private static class LogAndPrintln {
private static void i(@Nullable PrintWriter pw, String msg) {
Log.i(LOG_TAG, msg);
if (pw != null) {
pw.println(msg);
pw.flush();
}
}
private static void e(@Nullable PrintWriter pw, String msg) {
Log.e(LOG_TAG, msg);
if (pw != null) {
pw.println("ERROR: " + msg);
pw.flush();
}
}
private static void e(@Nullable PrintWriter pw, String msg, @NonNull Exception e) {
Log.e(LOG_TAG, msg, e);
if (pw != null) {
pw.println("ERROR: " + msg + " ::\n " + e);
pw.flush();
}
}
}
}