blob: 63539c4f9fd926c0f27ba9afd8415966007b35bc [file] [log] [blame]
/*
* Copyright (C) 2017 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.WindowManagerTraceFileProto.ENTRY;
import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.WHERE;
import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
import android.annotation.Nullable;
import android.content.Context;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.Trace;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
/**
* A class that allows window manager to dump its state continuously to a trace file, such that a
* time series of window manager state can be analyzed after the fact.
*/
class WindowTracing {
/**
* Maximum buffer size, currently defined as 512 KB
* Size was experimentally defined to fit between 100 to 150 elements.
*/
private static final int WINDOW_TRACE_BUFFER_SIZE = 512 * 1024;
private static final String TAG = "WindowTracing";
private final Object mLock = new Object();
private final WindowTraceBuffer.Builder mBufferBuilder;
private WindowTraceBuffer mTraceBuffer;
private boolean mEnabled;
private volatile boolean mEnabledLockFree;
WindowTracing(File file) {
mBufferBuilder = new WindowTraceBuffer.Builder()
.setTraceFile(file)
.setBufferCapacity(WINDOW_TRACE_BUFFER_SIZE);
}
void startTrace(@Nullable PrintWriter pw) throws IOException {
if (IS_USER) {
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
synchronized (mLock) {
logAndPrintln(pw, "Start tracing to " + mBufferBuilder.getFile() + ".");
if (mTraceBuffer != null) {
try {
mTraceBuffer.writeToDisk();
} catch (InterruptedException e) {
logAndPrintln(pw, "Error: Unable to flush the previous buffer.");
}
}
mTraceBuffer = mBufferBuilder.build();
mEnabled = mEnabledLockFree = true;
}
}
private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
Log.i(TAG, msg);
if (pw != null) {
pw.println(msg);
pw.flush();
}
}
void stopTrace(@Nullable PrintWriter pw) {
if (IS_USER) {
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
synchronized (mLock) {
logAndPrintln(pw, "Stop tracing to " + mBufferBuilder.getFile()
+ ". Waiting for traces to flush.");
mEnabled = mEnabledLockFree = false;
synchronized (mLock) {
if (mEnabled) {
logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
throw new IllegalStateException("tracing enabled while waiting for flush.");
}
try {
mTraceBuffer.writeToDisk();
} catch (IOException e) {
Log.e(TAG, "Unable to write buffer to file", e);
} catch (InterruptedException e) {
Log.e(TAG, "Unable to interrupt window tracing file write thread", e);
}
}
logAndPrintln(pw, "Trace written to " + mBufferBuilder.getFile() + ".");
}
}
private void appendTraceEntry(ProtoOutputStream proto) {
if (!mEnabledLockFree) {
return;
}
try {
mTraceBuffer.add(proto);
} catch (InterruptedException e) {
Log.e(TAG, "Unable to add element to trace", e);
Thread.currentThread().interrupt();
}
}
boolean isEnabled() {
return mEnabledLockFree;
}
static WindowTracing createDefaultAndStartLooper(Context context) {
File file = new File("/data/misc/wmtrace/wm_trace.pb");
return new WindowTracing(file);
}
int onShellCommand(ShellCommand shell, String cmd) {
PrintWriter pw = shell.getOutPrintWriter();
try {
switch (cmd) {
case "start":
startTrace(pw);
return 0;
case "stop":
stopTrace(pw);
return 0;
default:
pw.println("Unknown command: " + cmd);
return -1;
}
} catch (IOException e) {
logAndPrintln(pw, e.toString());
throw new RuntimeException(e);
}
}
void traceStateLocked(String where, WindowManagerService service) {
if (!isEnabled()) {
return;
}
ProtoOutputStream os = new ProtoOutputStream();
long tokenOuter = os.start(ENTRY);
os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
os.write(WHERE, where);
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
try {
long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
service.writeToProtoLocked(os, true /* trim */);
os.end(tokenInner);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
os.end(tokenOuter);
appendTraceEntry(os);
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}