/*
|
* 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.os.ShellCommand;
|
import android.os.SystemClock;
|
import android.os.Trace;
|
import android.util.Log;
|
import android.util.proto.ProtoOutputStream;
|
import android.view.Choreographer;
|
|
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 BUFFER_CAPACITY_CRITICAL = 512 * 1024;
|
private static final int BUFFER_CAPACITY_TRIM = 2048 * 1024;
|
private static final int BUFFER_CAPACITY_ALL = 4096 * 1024;
|
private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace.pb";
|
private static final String TAG = "WindowTracing";
|
|
private final WindowManagerService mService;
|
private final Choreographer mChoreographer;
|
private final WindowManagerGlobalLock mGlobalLock;
|
|
private final Object mEnabledLock = new Object();
|
private final File mTraceFile;
|
private final WindowTraceBuffer mBuffer;
|
private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) ->
|
log("onFrame" /* where */);
|
|
private @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM;
|
private boolean mLogOnFrame = false;
|
private boolean mEnabled;
|
private volatile boolean mEnabledLockFree;
|
private boolean mScheduled;
|
|
static WindowTracing createDefaultAndStartLooper(WindowManagerService service,
|
Choreographer choreographer) {
|
File file = new File(TRACE_FILENAME);
|
return new WindowTracing(file, service, choreographer, BUFFER_CAPACITY_TRIM);
|
}
|
|
private WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
|
int bufferCapacity) {
|
this(file, service, choreographer, service.mGlobalLock, bufferCapacity);
|
}
|
|
WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
|
WindowManagerGlobalLock globalLock, int bufferCapacity) {
|
mChoreographer = choreographer;
|
mService = service;
|
mGlobalLock = globalLock;
|
mTraceFile = file;
|
mBuffer = new WindowTraceBuffer(bufferCapacity);
|
setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */);
|
}
|
|
void startTrace(@Nullable PrintWriter pw) {
|
if (IS_USER) {
|
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
|
return;
|
}
|
synchronized (mEnabledLock) {
|
logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
|
mBuffer.resetBuffer();
|
mEnabled = mEnabledLockFree = true;
|
}
|
log("trace.enable");
|
}
|
|
/**
|
* Stops the trace and write the current buffer to disk
|
* @param pw Print writer
|
*/
|
void stopTrace(@Nullable PrintWriter pw) {
|
stopTrace(pw, true /* writeToFile */);
|
}
|
|
/**
|
* Stops the trace
|
* @param pw Print writer
|
* @param writeToFile If the current buffer should be written to disk or not
|
*/
|
void stopTrace(@Nullable PrintWriter pw, boolean writeToFile) {
|
if (IS_USER) {
|
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
|
return;
|
}
|
synchronized (mEnabledLock) {
|
logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
|
mEnabled = mEnabledLockFree = false;
|
|
if (mEnabled) {
|
logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
|
throw new IllegalStateException("tracing enabled while waiting for flush.");
|
}
|
if (writeToFile) {
|
writeTraceToFileLocked();
|
logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
|
}
|
}
|
}
|
|
private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
|
logAndPrintln(pw, "Setting window tracing log level to " + logLevel);
|
mLogLevel = logLevel;
|
|
switch (logLevel) {
|
case WindowTraceLogLevel.ALL: {
|
setBufferCapacity(BUFFER_CAPACITY_ALL, pw);
|
break;
|
}
|
case WindowTraceLogLevel.TRIM: {
|
setBufferCapacity(BUFFER_CAPACITY_TRIM, pw);
|
break;
|
}
|
case WindowTraceLogLevel.CRITICAL: {
|
setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw);
|
break;
|
}
|
}
|
}
|
|
private void setLogFrequency(boolean onFrame, PrintWriter pw) {
|
logAndPrintln(pw, "Setting window tracing log frequency to "
|
+ ((onFrame) ? "frame" : "transaction"));
|
mLogOnFrame = onFrame;
|
}
|
|
private void setBufferCapacity(int capacity, PrintWriter pw) {
|
logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes");
|
mBuffer.setCapacity(capacity);
|
}
|
|
boolean isEnabled() {
|
return mEnabledLockFree;
|
}
|
|
int onShellCommand(ShellCommand shell) {
|
PrintWriter pw = shell.getOutPrintWriter();
|
String cmd = shell.getNextArgRequired();
|
switch (cmd) {
|
case "start":
|
startTrace(pw);
|
return 0;
|
case "stop":
|
stopTrace(pw);
|
return 0;
|
case "status":
|
logAndPrintln(pw, getStatus());
|
return 0;
|
case "frame":
|
setLogFrequency(true /* onFrame */, pw);
|
mBuffer.resetBuffer();
|
return 0;
|
case "transaction":
|
setLogFrequency(false /* onFrame */, pw);
|
mBuffer.resetBuffer();
|
return 0;
|
case "level":
|
String logLevelStr = shell.getNextArgRequired().toLowerCase();
|
switch (logLevelStr) {
|
case "all": {
|
setLogLevel(WindowTraceLogLevel.ALL, pw);
|
break;
|
}
|
case "trim": {
|
setLogLevel(WindowTraceLogLevel.TRIM, pw);
|
break;
|
}
|
case "critical": {
|
setLogLevel(WindowTraceLogLevel.CRITICAL, pw);
|
break;
|
}
|
default: {
|
setLogLevel(WindowTraceLogLevel.TRIM, pw);
|
break;
|
}
|
}
|
mBuffer.resetBuffer();
|
return 0;
|
case "size":
|
setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw);
|
mBuffer.resetBuffer();
|
return 0;
|
default:
|
pw.println("Unknown command: " + cmd);
|
pw.println("Window manager trace options:");
|
pw.println(" start: Start logging");
|
pw.println(" stop: Stop logging");
|
pw.println(" frame: Log trace once per frame");
|
pw.println(" transaction: Log each transaction");
|
pw.println(" size: Set the maximum log size (in KB)");
|
pw.println(" status: Print trace status");
|
pw.println(" level [lvl]: Set the log level between");
|
pw.println(" lvl may be one of:");
|
pw.println(" critical: Only visible windows with reduced information");
|
pw.println(" trim: All windows with reduced");
|
pw.println(" all: All window and information");
|
return -1;
|
}
|
}
|
|
String getStatus() {
|
return "Status: "
|
+ ((isEnabled()) ? "Enabled" : "Disabled")
|
+ "\n"
|
+ "Log level: "
|
+ mLogLevel
|
+ "\n"
|
+ mBuffer.getStatus();
|
}
|
|
/**
|
* If tracing is enabled, log the current state or schedule the next frame to be logged,
|
* according to {@link #mLogOnFrame}.
|
*
|
* @param where Logging point descriptor
|
*/
|
void logState(String where) {
|
if (!isEnabled()) {
|
return;
|
}
|
|
if (mLogOnFrame) {
|
schedule();
|
} else {
|
log(where);
|
}
|
}
|
|
/**
|
* Schedule the log to trace the next frame
|
*/
|
private void schedule() {
|
if (mScheduled) {
|
return;
|
}
|
|
mScheduled = true;
|
mChoreographer.postFrameCallback(mFrameCallback);
|
}
|
|
/**
|
* Write the current frame to the buffer
|
*
|
* @param where Logging point descriptor
|
*/
|
private void log(String where) {
|
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
|
try {
|
ProtoOutputStream os = new ProtoOutputStream();
|
long tokenOuter = os.start(ENTRY);
|
os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
|
os.write(WHERE, where);
|
|
long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
|
synchronized (mGlobalLock) {
|
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
|
try {
|
mService.writeToProtoLocked(os, mLogLevel);
|
} finally {
|
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
|
}
|
}
|
os.end(tokenInner);
|
os.end(tokenOuter);
|
mBuffer.add(os);
|
mScheduled = false;
|
} catch (Exception e) {
|
Log.wtf(TAG, "Exception while tracing state", e);
|
} finally {
|
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
|
}
|
}
|
|
/**
|
* Writes the trace buffer to new file for the bugreport.
|
*
|
* This method is synchronized with {@code #startTrace(PrintWriter)} and
|
* {@link #stopTrace(PrintWriter)}.
|
*/
|
void writeTraceToFile() {
|
synchronized (mEnabledLock) {
|
writeTraceToFileLocked();
|
}
|
}
|
|
private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
|
Log.i(TAG, msg);
|
if (pw != null) {
|
pw.println(msg);
|
pw.flush();
|
}
|
}
|
|
/**
|
* Writes the trace buffer to disk. This method has no internal synchronization and should be
|
* externally synchronized
|
*/
|
private void writeTraceToFileLocked() {
|
try {
|
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
|
mBuffer.writeTraceToFile(mTraceFile);
|
} catch (IOException e) {
|
Log.e(TAG, "Unable to write buffer to file", e);
|
} finally {
|
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
|
}
|
}
|
}
|