blob: b1951e03817724738d884ba02dd11fd0078cae93 [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.ChangeInfo.CHANGE_FLAGS;
import static com.android.server.wm.shell.ChangeInfo.HAS_CHANGED;
import static com.android.server.wm.shell.ChangeInfo.TRANSIT_MODE;
import static com.android.server.wm.shell.ChangeInfo.WINDOW_IDENTIFIER;
import static com.android.server.wm.shell.Transition.CHANGE;
import static com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID;
import static com.android.server.wm.shell.Transition.FLAGS;
import static com.android.server.wm.shell.Transition.ID;
import static com.android.server.wm.shell.Transition.START_TRANSACTION_ID;
import static com.android.server.wm.shell.Transition.STATE;
import static com.android.server.wm.shell.Transition.TIMESTAMP;
import static com.android.server.wm.shell.Transition.TRANSITION_TYPE;
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.TRANSITION;
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 com.android.server.wm.shell.TransitionTraceProto;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Helper class to collect and dump transition traces.
*/
public class TransitionTracer {
private static final String LOG_TAG = "TransitionTracer";
/**
* Maximum buffer size, currently defined as 5 MB
*/
private static final int BUFFER_CAPACITY = 5120 * 1024; // 5 MB
static final String WINSCOPE_EXT = ".winscope";
private static final String TRACE_FILE = "/data/misc/wmtrace/transition_trace" + WINSCOPE_EXT;
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
private final TransitionTraceBuffer mTraceBuffer = new TransitionTraceBuffer();
private final Object mEnabledLock = new Object();
private volatile boolean mEnabled = false;
private long mTraceStartTimestamp;
private class TransitionTraceBuffer {
private final TraceBuffer mBuffer = new TraceBuffer(BUFFER_CAPACITY);
private void pushTransitionState(Transition transition) {
final ProtoOutputStream outputStream = new ProtoOutputStream();
final long transitionEntryToken = outputStream.start(TRANSITION);
outputStream.write(ID, transition.getDebugId());
outputStream.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
outputStream.write(TRANSITION_TYPE, transition.mType);
outputStream.write(STATE, transition.getState());
outputStream.write(FLAGS, transition.getFlags());
if (transition.getStartTransaction() != null) {
outputStream.write(START_TRANSACTION_ID, transition.getStartTransaction().getId());
}
if (transition.getFinishTransaction() != null) {
outputStream.write(FINISH_TRANSACTION_ID,
transition.getFinishTransaction().getId());
}
for (int i = 0; i < transition.mChanges.size(); ++i) {
final WindowContainer window = transition.mChanges.keyAt(i);
final ChangeInfo changeInfo = transition.mChanges.valueAt(i);
writeChange(outputStream, window, changeInfo);
}
outputStream.end(transitionEntryToken);
mBuffer.add(outputStream);
}
private void writeChange(ProtoOutputStream outputStream, WindowContainer window,
ChangeInfo changeInfo) {
Trace.beginSection("TransitionProto#addChange");
final long changeEntryToken = outputStream.start(CHANGE);
final int transitMode = changeInfo.getTransitMode(window);
final boolean hasChanged = changeInfo.hasChanged(window);
final int changeFlags = changeInfo.getChangeFlags(window);
outputStream.write(TRANSIT_MODE, transitMode);
outputStream.write(HAS_CHANGED, hasChanged);
outputStream.write(CHANGE_FLAGS, changeFlags);
window.writeIdentifierToProto(outputStream, WINDOW_IDENTIFIER);
outputStream.end(changeEntryToken);
Trace.endSection();
}
public void writeToFile(File file, ProtoOutputStream proto) throws IOException {
mBuffer.writeTraceToFile(file, proto);
}
public void reset() {
mBuffer.resetBuffer();
}
}
/**
* Records the current state of a transition in the transition trace (if it is running).
* @param transition the transition that we want to record the state of.
*/
public void logState(com.android.server.wm.Transition transition) {
if (!mEnabled) {
return;
}
Log.d(LOG_TAG, "Logging state of transition " + transition);
mTraceBuffer.pushTransitionState(transition);
}
/**
* 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) {
mTraceStartTimestamp = SystemClock.elapsedRealtime();
mEnabled = true;
mTraceBuffer.reset();
}
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) {
if (!mEnabled) {
LogAndPrintln.e(pw,
"Error: Tracing can't be stopped because it hasn't been started.");
return;
}
mEnabled = false;
writeTraceToFileLocked(pw, outputFile);
}
Trace.endSection();
}
boolean isEnabled() {
return mEnabled;
}
private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
try {
ProtoOutputStream proto = new ProtoOutputStream();
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
proto.write(TransitionTraceProto.TIMESTAMP, mTraceStartTimestamp);
int pid = android.os.Process.myPid();
LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
+ " from process " + pid);
mTraceBuffer.writeToFile(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();
}
}
}
}