Merge "Remove on-disk WM trace logging"
This commit is contained in:
committed by
Android (Google) Code Review
commit
d6ea41c22e
@@ -5090,11 +5090,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
|
||||
@Override
|
||||
public void startWindowTrace(){
|
||||
try {
|
||||
mWindowTracing.startTrace(null /* printwriter */);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
mWindowTracing.startTrace(null /* printwriter */);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,7 +20,6 @@ import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
|
||||
import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
|
||||
import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
|
||||
|
||||
import android.os.Trace;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -36,24 +35,30 @@ import java.util.Queue;
|
||||
/**
|
||||
* Buffer used for window tracing.
|
||||
*/
|
||||
abstract class WindowTraceBuffer {
|
||||
class WindowTraceBuffer {
|
||||
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
|
||||
|
||||
final Object mBufferLock = new Object();
|
||||
final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>();
|
||||
final File mTraceFile;
|
||||
int mBufferSize;
|
||||
private final int mBufferCapacity;
|
||||
private final Object mBufferLock = new Object();
|
||||
|
||||
WindowTraceBuffer(int size, File traceFile) throws IOException {
|
||||
mBufferCapacity = size;
|
||||
mTraceFile = traceFile;
|
||||
private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>();
|
||||
private int mBufferUsedSize;
|
||||
private int mBufferCapacity;
|
||||
|
||||
initTraceFile();
|
||||
WindowTraceBuffer(int bufferCapacity) {
|
||||
mBufferCapacity = bufferCapacity;
|
||||
resetBuffer();
|
||||
}
|
||||
|
||||
int getAvailableSpace() {
|
||||
return mBufferCapacity - mBufferSize;
|
||||
return mBufferCapacity - mBufferUsedSize;
|
||||
}
|
||||
|
||||
int size() {
|
||||
return mBuffer.size();
|
||||
}
|
||||
|
||||
void setCapacity(int capacity) {
|
||||
mBufferCapacity = capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,42 +75,37 @@ abstract class WindowTraceBuffer {
|
||||
+ mBufferCapacity + " Object size: " + protoLength);
|
||||
}
|
||||
synchronized (mBufferLock) {
|
||||
boolean canAdd = canAdd(protoLength);
|
||||
if (canAdd) {
|
||||
mBuffer.add(proto);
|
||||
mBufferSize += protoLength;
|
||||
}
|
||||
discardOldest(protoLength);
|
||||
mBuffer.add(proto);
|
||||
mBufferUsedSize += protoLength;
|
||||
mBufferLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the buffer execution and flush all buffer content to the disk.
|
||||
*
|
||||
* @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile}
|
||||
*/
|
||||
void dump() throws IOException, InterruptedException {
|
||||
try {
|
||||
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFile");
|
||||
writeTraceToFile();
|
||||
} finally {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean contains(byte[] other) {
|
||||
return mBuffer.stream()
|
||||
.anyMatch(p -> Arrays.equals(p.getBytes(), other));
|
||||
}
|
||||
|
||||
private void initTraceFile() throws IOException {
|
||||
mTraceFile.delete();
|
||||
try (OutputStream os = new FileOutputStream(mTraceFile)) {
|
||||
mTraceFile.setReadable(true, false);
|
||||
ProtoOutputStream proto = new ProtoOutputStream(os);
|
||||
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
|
||||
proto.flush();
|
||||
/**
|
||||
* Writes the trace buffer to disk.
|
||||
*/
|
||||
void writeTraceToFile(File traceFile) throws IOException {
|
||||
synchronized (mBufferLock) {
|
||||
traceFile.delete();
|
||||
traceFile.setReadable(true, false);
|
||||
try (OutputStream os = new FileOutputStream(traceFile)) {
|
||||
ProtoOutputStream proto = new ProtoOutputStream();
|
||||
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
|
||||
os.write(proto.getBytes());
|
||||
while (!mBuffer.isEmpty()) {
|
||||
proto = mBuffer.poll();
|
||||
mBufferUsedSize -= proto.getRawSize();
|
||||
byte[] protoBytes = proto.getBytes();
|
||||
os.write(protoBytes);
|
||||
}
|
||||
os.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,59 +114,48 @@ abstract class WindowTraceBuffer {
|
||||
* smaller than the overall buffer size.
|
||||
*
|
||||
* @param protoLength byte array representation of the Proto object to add
|
||||
* @return {@code true} if the element can be added to the buffer or not
|
||||
*/
|
||||
abstract boolean canAdd(int protoLength);
|
||||
private void discardOldest(int protoLength) {
|
||||
long availableSpace = getAvailableSpace();
|
||||
|
||||
while (availableSpace < protoLength) {
|
||||
|
||||
ProtoOutputStream item = mBuffer.poll();
|
||||
if (item == null) {
|
||||
throw new IllegalStateException("No element to discard from buffer");
|
||||
}
|
||||
mBufferUsedSize -= item.getRawSize();
|
||||
availableSpace = getAvailableSpace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all buffer content to the disk.
|
||||
*
|
||||
* @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile}
|
||||
* Removes all elements form the buffer
|
||||
*/
|
||||
abstract void writeTraceToFile() throws IOException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceRingBuffer} for
|
||||
* continuous mode or a {@link WindowTraceQueueBuffer} otherwise
|
||||
*/
|
||||
static class Builder {
|
||||
private boolean mContinuous;
|
||||
private File mTraceFile;
|
||||
private int mBufferCapacity;
|
||||
|
||||
Builder setContinuousMode(boolean continuous) {
|
||||
mContinuous = continuous;
|
||||
return this;
|
||||
void resetBuffer() {
|
||||
synchronized (mBufferLock) {
|
||||
mBuffer.clear();
|
||||
mBufferUsedSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Builder setTraceFile(File traceFile) {
|
||||
mTraceFile = traceFile;
|
||||
return this;
|
||||
}
|
||||
@VisibleForTesting
|
||||
int getBufferSize() {
|
||||
return mBufferUsedSize;
|
||||
}
|
||||
|
||||
Builder setBufferCapacity(int size) {
|
||||
mBufferCapacity = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
File getFile() {
|
||||
return mTraceFile;
|
||||
}
|
||||
|
||||
WindowTraceBuffer build() throws IOException {
|
||||
if (mBufferCapacity <= 0) {
|
||||
throw new IllegalStateException("Buffer capacity must be greater than 0.");
|
||||
}
|
||||
|
||||
if (mTraceFile == null) {
|
||||
throw new IllegalArgumentException("A valid trace file must be specified.");
|
||||
}
|
||||
|
||||
if (mContinuous) {
|
||||
return new WindowTraceRingBuffer(mBufferCapacity, mTraceFile);
|
||||
} else {
|
||||
return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile);
|
||||
}
|
||||
String getStatus() {
|
||||
synchronized (mBufferLock) {
|
||||
return "Buffer size: "
|
||||
+ mBufferCapacity
|
||||
+ " bytes"
|
||||
+ "\n"
|
||||
+ "Buffer usage: "
|
||||
+ mBufferUsedSize
|
||||
+ " bytes"
|
||||
+ "\n"
|
||||
+ "Elements in the buffer: "
|
||||
+ mBuffer.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 android.util.Log;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* A buffer structure backed by a {@link java.util.concurrent.BlockingQueue} to store the first
|
||||
* {@code #size size} bytes of window trace elements.
|
||||
* Once the buffer is full it will no longer accepts new elements.
|
||||
*/
|
||||
class WindowTraceQueueBuffer extends WindowTraceBuffer {
|
||||
private static final String TAG = "WindowTracing";
|
||||
|
||||
private Thread mConsumerThread;
|
||||
private boolean mCancel;
|
||||
|
||||
@VisibleForTesting
|
||||
WindowTraceQueueBuffer(int size, File traceFile, boolean startConsumerThread)
|
||||
throws IOException {
|
||||
super(size, traceFile);
|
||||
if (startConsumerThread) {
|
||||
initializeConsumerThread();
|
||||
}
|
||||
}
|
||||
|
||||
WindowTraceQueueBuffer(int size, File traceFile) throws IOException {
|
||||
this(size, traceFile, !IS_USER);
|
||||
}
|
||||
|
||||
private void initializeConsumerThread() {
|
||||
mCancel = false;
|
||||
mConsumerThread = new Thread(() -> {
|
||||
try {
|
||||
loop();
|
||||
} catch (InterruptedException e) {
|
||||
Log.i(TAG, "Interrupting trace consumer thread");
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to execute trace consumer thread", e);
|
||||
}
|
||||
}, "window_tracing");
|
||||
mConsumerThread.start();
|
||||
}
|
||||
|
||||
private void loop() throws IOException, InterruptedException {
|
||||
while (!mCancel) {
|
||||
ProtoOutputStream proto;
|
||||
synchronized (mBufferLock) {
|
||||
mBufferLock.wait();
|
||||
proto = mBuffer.poll();
|
||||
if (proto != null) {
|
||||
mBufferSize -= proto.getRawSize();
|
||||
}
|
||||
}
|
||||
if (proto != null) {
|
||||
try (OutputStream os = new FileOutputStream(mTraceFile, true)) {
|
||||
byte[] protoBytes = proto.getBytes();
|
||||
os.write(protoBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean canAdd(int protoLength) {
|
||||
long availableSpace = getAvailableSpace();
|
||||
return availableSpace >= protoLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeTraceToFile() throws InterruptedException {
|
||||
synchronized (mBufferLock) {
|
||||
mCancel = true;
|
||||
mBufferLock.notify();
|
||||
}
|
||||
if (mConsumerThread != null) {
|
||||
mConsumerThread.join();
|
||||
mConsumerThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 android.util.proto.ProtoOutputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* A ring buffer to store the {@code #size size} bytes of window trace data.
|
||||
* The buffer operates on a trace entry level, that is, if the new trace data is larger than the
|
||||
* available buffer space, the buffer will discard as many full trace entries as necessary to fit
|
||||
* the new trace.
|
||||
*/
|
||||
class WindowTraceRingBuffer extends WindowTraceBuffer {
|
||||
WindowTraceRingBuffer(int size, File traceFile) throws IOException {
|
||||
super(size, traceFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean canAdd(int protoLength) {
|
||||
long availableSpace = getAvailableSpace();
|
||||
|
||||
while (availableSpace < protoLength) {
|
||||
discardOldest();
|
||||
availableSpace = getAvailableSpace();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeTraceToFile() throws IOException {
|
||||
synchronized (mBufferLock) {
|
||||
try (OutputStream os = new FileOutputStream(mTraceFile, true)) {
|
||||
while (!mBuffer.isEmpty()) {
|
||||
ProtoOutputStream proto = mBuffer.poll();
|
||||
mBufferSize -= proto.getRawSize();
|
||||
byte[] protoBytes = proto.getBytes();
|
||||
os.write(protoBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void discardOldest() {
|
||||
ProtoOutputStream item = mBuffer.poll();
|
||||
if (item == null) {
|
||||
throw new IllegalStateException("No element to discard from buffer");
|
||||
}
|
||||
mBufferSize -= item.getRawSize();
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,6 @@ import android.util.Log;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
import android.view.Choreographer;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
@@ -47,139 +45,191 @@ 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 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 mLock = new Object();
|
||||
private final WindowTraceBuffer.Builder mBufferBuilder;
|
||||
private final Object mEnabledLock = new Object();
|
||||
private final File mTraceFile;
|
||||
private final WindowTraceBuffer mBuffer;
|
||||
private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) ->
|
||||
log("onFrame" /* where */);
|
||||
|
||||
private WindowTraceBuffer mTraceBuffer;
|
||||
|
||||
private @WindowTraceLogLevel int mWindowTraceLogLevel = WindowTraceLogLevel.TRIM;
|
||||
private boolean mContinuousMode;
|
||||
private @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM;
|
||||
private boolean mLogOnFrame = false;
|
||||
private boolean mEnabled;
|
||||
private volatile boolean mEnabledLockFree;
|
||||
private boolean mScheduled;
|
||||
private Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) ->
|
||||
log("onFrame" /* where */);
|
||||
|
||||
private WindowTracing(File file, WindowManagerService service, Choreographer choreographer) {
|
||||
this(file, service, choreographer, service.mGlobalLock);
|
||||
static WindowTracing createDefaultAndStartLooper(WindowManagerService service,
|
||||
Choreographer choreographer) {
|
||||
File file = new File(TRACE_FILENAME);
|
||||
return new WindowTracing(file, service, choreographer, BUFFER_CAPACITY_TRIM);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
|
||||
WindowManagerGlobalLock globalLock) {
|
||||
mBufferBuilder = new WindowTraceBuffer.Builder()
|
||||
.setTraceFile(file)
|
||||
.setBufferCapacity(WINDOW_TRACE_BUFFER_SIZE);
|
||||
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) throws IOException {
|
||||
void startTrace(@Nullable PrintWriter pw) {
|
||||
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) {
|
||||
writeTraceToFileLocked();
|
||||
}
|
||||
mTraceBuffer = mBufferBuilder
|
||||
.setContinuousMode(mContinuousMode)
|
||||
.build();
|
||||
synchronized (mEnabledLock) {
|
||||
logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
|
||||
mBuffer.resetBuffer();
|
||||
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.");
|
||||
synchronized (mEnabledLock) {
|
||||
logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". 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.");
|
||||
}
|
||||
writeTraceToFileLocked();
|
||||
mTraceBuffer = null;
|
||||
if (mEnabled) {
|
||||
logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
|
||||
throw new IllegalStateException("tracing enabled while waiting for flush.");
|
||||
}
|
||||
logAndPrintln(pw, "Trace written to " + mBufferBuilder.getFile() + ".");
|
||||
writeTraceToFileLocked();
|
||||
logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setContinuousMode(boolean continuous, PrintWriter pw) {
|
||||
logAndPrintln(pw, "Setting window tracing continuous mode to " + continuous);
|
||||
private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
|
||||
logAndPrintln(pw, "Setting window tracing log level to " + logLevel);
|
||||
mLogLevel = logLevel;
|
||||
|
||||
if (mEnabled) {
|
||||
logAndPrintln(pw, "Trace is currently active, change will take effect once the "
|
||||
+ "trace is restarted.");
|
||||
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;
|
||||
}
|
||||
}
|
||||
mContinuousMode = continuous;
|
||||
mWindowTraceLogLevel = (continuous) ? WindowTraceLogLevel.CRITICAL :
|
||||
WindowTraceLogLevel.TRIM;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static WindowTracing createDefaultAndStartLooper(WindowManagerService service,
|
||||
Choreographer choreographer) {
|
||||
File file = new File("/data/misc/wmtrace/wm_trace.pb");
|
||||
return new WindowTracing(file, service, choreographer);
|
||||
}
|
||||
|
||||
int onShellCommand(ShellCommand shell) {
|
||||
PrintWriter pw = shell.getOutPrintWriter();
|
||||
try {
|
||||
String cmd = shell.getNextArgRequired();
|
||||
switch (cmd) {
|
||||
case "start":
|
||||
startTrace(pw);
|
||||
return 0;
|
||||
case "stop":
|
||||
stopTrace(pw);
|
||||
return 0;
|
||||
case "continuous":
|
||||
setContinuousMode(Boolean.valueOf(shell.getNextArgRequired()), pw);
|
||||
return 0;
|
||||
default:
|
||||
pw.println("Unknown command: " + cmd);
|
||||
return -1;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logAndPrintln(pw, e.toString());
|
||||
throw new RuntimeException(e);
|
||||
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(" 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;
|
||||
}
|
||||
}
|
||||
|
||||
private 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 #mContinuousMode}.
|
||||
* according to {@link #mLogOnFrame}.
|
||||
*
|
||||
* @param where Logging point descriptor
|
||||
*/
|
||||
@@ -188,7 +238,7 @@ class WindowTracing {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mContinuousMode) {
|
||||
if (mLogOnFrame) {
|
||||
schedule();
|
||||
} else {
|
||||
log(where);
|
||||
@@ -215,25 +265,24 @@ class WindowTracing {
|
||||
private void log(String where) {
|
||||
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
|
||||
try {
|
||||
synchronized (mGlobalLock) {
|
||||
ProtoOutputStream os = new ProtoOutputStream();
|
||||
long tokenOuter = os.start(ENTRY);
|
||||
os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
|
||||
os.write(WHERE, where);
|
||||
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 {
|
||||
long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
|
||||
mService.writeToProtoLocked(os, mWindowTraceLogLevel);
|
||||
os.end(tokenInner);
|
||||
mService.writeToProtoLocked(os, mLogLevel);
|
||||
} finally {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
|
||||
}
|
||||
os.end(tokenOuter);
|
||||
mTraceBuffer.add(os);
|
||||
|
||||
mScheduled = false;
|
||||
}
|
||||
os.end(tokenInner);
|
||||
os.end(tokenOuter);
|
||||
mBuffer.add(os);
|
||||
mScheduled = false;
|
||||
} catch (Exception e) {
|
||||
Log.wtf(TAG, "Exception while tracing state", e);
|
||||
} finally {
|
||||
@@ -242,31 +291,37 @@ class WindowTracing {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the trace buffer to disk. This method has no internal synchronization and should be
|
||||
* externally synchronized
|
||||
*/
|
||||
private void writeTraceToFileLocked() {
|
||||
if (mTraceBuffer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mTraceBuffer.dump();
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the trace buffer to disk and clones it into a new file for the bugreport.
|
||||
* 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 (mLock) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,11 +36,10 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Test class for {@link WindowTraceBuffer} and {@link WindowTraceQueueBuffer}.
|
||||
* Test class for {@link WindowTraceBuffer}.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest WmTests:WindowTraceBufferTest
|
||||
@@ -49,12 +48,15 @@ import java.io.IOException;
|
||||
@Presubmit
|
||||
public class WindowTraceBufferTest {
|
||||
private File mFile;
|
||||
private WindowTraceBuffer mBuffer;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
final Context testContext = getInstrumentation().getContext();
|
||||
mFile = testContext.getFileStreamPath("tracing_test.dat");
|
||||
mFile.delete();
|
||||
|
||||
mBuffer = new WindowTraceBuffer(10);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -63,145 +65,112 @@ public class WindowTraceBufferTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTraceQueueBuffer_addItem() throws Exception {
|
||||
ProtoOutputStream toWrite1 = getDummy(1);
|
||||
ProtoOutputStream toWrite2 = getDummy(2);
|
||||
ProtoOutputStream toWrite3 = getDummy(3);
|
||||
final int objectSize = toWrite1.getRawSize();
|
||||
final int bufferCapacity = objectSize * 2;
|
||||
|
||||
final WindowTraceBuffer buffer = buildQueueBuffer(bufferCapacity);
|
||||
|
||||
buffer.add(toWrite1);
|
||||
byte[] toWrite1Bytes = toWrite1.getBytes();
|
||||
assertTrue("First element should be in the list",
|
||||
buffer.contains(toWrite1Bytes));
|
||||
|
||||
buffer.add(toWrite2);
|
||||
byte[] toWrite2Bytes = toWrite2.getBytes();
|
||||
assertTrue("First element should be in the list",
|
||||
buffer.contains(toWrite1Bytes));
|
||||
assertTrue("Second element should be in the list",
|
||||
buffer.contains(toWrite2Bytes));
|
||||
|
||||
buffer.add(toWrite3);
|
||||
byte[] toWrite3Bytes = toWrite3.getBytes();
|
||||
assertTrue("First element should be in the list",
|
||||
buffer.contains(toWrite1Bytes));
|
||||
assertTrue("Second element should be in the list",
|
||||
buffer.contains(toWrite2Bytes));
|
||||
assertTrue("Third element should not be in the list",
|
||||
!buffer.contains(toWrite3Bytes));
|
||||
|
||||
assertEquals("Buffer should have 2 elements", buffer.mBuffer.size(), 2);
|
||||
assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity),
|
||||
buffer.mBufferSize, bufferCapacity);
|
||||
assertEquals("Buffer is full, available space should be 0",
|
||||
buffer.getAvailableSpace(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTraceRingBuffer_addItem() throws Exception {
|
||||
public void test_addItem() {
|
||||
ProtoOutputStream toWrite = getDummy(1);
|
||||
final int objectSize = toWrite.getRawSize();
|
||||
mBuffer.setCapacity(objectSize);
|
||||
mBuffer.resetBuffer();
|
||||
|
||||
final WindowTraceBuffer buffer = buildRingBuffer(objectSize);
|
||||
Preconditions.checkArgument(mBuffer.size() == 0);
|
||||
|
||||
Preconditions.checkArgument(buffer.mBuffer.isEmpty());
|
||||
mBuffer.add(toWrite);
|
||||
|
||||
buffer.add(toWrite);
|
||||
|
||||
assertEquals("Item was not added to the buffer", buffer.mBuffer.size(), 1);
|
||||
assertEquals("Item was not added to the buffer", 1, mBuffer.size());
|
||||
assertEquals("Total buffer getSize differs from inserted object",
|
||||
buffer.mBufferSize, objectSize);
|
||||
assertEquals("Available buffer space does not match used one",
|
||||
buffer.getAvailableSpace(), 0);
|
||||
mBuffer.getBufferSize(), objectSize);
|
||||
assertEquals("Available buffer space does not match used one", 0,
|
||||
mBuffer.getAvailableSpace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTraceRingBuffer_addItemMustOverwriteOne() throws Exception {
|
||||
public void test_addItemMustOverwriteOne() {
|
||||
ProtoOutputStream toWrite1 = getDummy(1);
|
||||
ProtoOutputStream toWrite2 = getDummy(2);
|
||||
ProtoOutputStream toWrite3 = getDummy(3);
|
||||
final int objectSize = toWrite1.getRawSize();
|
||||
|
||||
final int bufferCapacity = objectSize * 2 + 1;
|
||||
final WindowTraceBuffer buffer = buildRingBuffer(bufferCapacity);
|
||||
mBuffer.setCapacity(bufferCapacity);
|
||||
mBuffer.resetBuffer();
|
||||
|
||||
buffer.add(toWrite1);
|
||||
mBuffer.add(toWrite1);
|
||||
byte[] toWrite1Bytes = toWrite1.getBytes();
|
||||
assertTrue("First element should be in the list",
|
||||
buffer.contains(toWrite1Bytes));
|
||||
mBuffer.contains(toWrite1Bytes));
|
||||
|
||||
buffer.add(toWrite2);
|
||||
mBuffer.add(toWrite2);
|
||||
byte[] toWrite2Bytes = toWrite2.getBytes();
|
||||
assertTrue("First element should be in the list",
|
||||
buffer.contains(toWrite1Bytes));
|
||||
mBuffer.contains(toWrite1Bytes));
|
||||
assertTrue("Second element should be in the list",
|
||||
buffer.contains(toWrite2Bytes));
|
||||
mBuffer.contains(toWrite2Bytes));
|
||||
|
||||
buffer.add(toWrite3);
|
||||
mBuffer.add(toWrite3);
|
||||
byte[] toWrite3Bytes = toWrite3.getBytes();
|
||||
assertTrue("First element should not be in the list",
|
||||
!buffer.contains(toWrite1Bytes));
|
||||
!mBuffer.contains(toWrite1Bytes));
|
||||
assertTrue("Second element should be in the list",
|
||||
buffer.contains(toWrite2Bytes));
|
||||
mBuffer.contains(toWrite2Bytes));
|
||||
assertTrue("Third element should be in the list",
|
||||
buffer.contains(toWrite3Bytes));
|
||||
assertEquals("Buffer should have 2 elements", buffer.mBuffer.size(), 2);
|
||||
mBuffer.contains(toWrite3Bytes));
|
||||
assertEquals("Buffer should have 2 elements", 2, mBuffer.size());
|
||||
assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity),
|
||||
buffer.mBufferSize, bufferCapacity - 1);
|
||||
assertEquals(" Buffer is full, available space should be 0",
|
||||
buffer.getAvailableSpace(), 1);
|
||||
mBuffer.getBufferSize(), bufferCapacity - 1);
|
||||
assertEquals(" Buffer is full, available space should be 0", 1,
|
||||
mBuffer.getAvailableSpace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTraceRingBuffer_addItemMustOverwriteMultiple() throws Exception {
|
||||
public void test_addItemMustOverwriteMultiple() {
|
||||
ProtoOutputStream toWriteSmall1 = getDummy(1);
|
||||
ProtoOutputStream toWriteSmall2 = getDummy(2);
|
||||
final int objectSize = toWriteSmall1.getRawSize();
|
||||
|
||||
final int bufferCapacity = objectSize * 2;
|
||||
final WindowTraceBuffer buffer = buildRingBuffer(bufferCapacity);
|
||||
mBuffer.setCapacity(bufferCapacity);
|
||||
mBuffer.resetBuffer();
|
||||
|
||||
ProtoOutputStream toWriteBig = new ProtoOutputStream();
|
||||
toWriteBig.write(MAGIC_NUMBER, 1);
|
||||
toWriteBig.write(MAGIC_NUMBER, 2);
|
||||
|
||||
buffer.add(toWriteSmall1);
|
||||
mBuffer.add(toWriteSmall1);
|
||||
byte[] toWriteSmall1Bytes = toWriteSmall1.getBytes();
|
||||
assertTrue("First element should be in the list",
|
||||
buffer.contains(toWriteSmall1Bytes));
|
||||
mBuffer.contains(toWriteSmall1Bytes));
|
||||
|
||||
buffer.add(toWriteSmall2);
|
||||
mBuffer.add(toWriteSmall2);
|
||||
byte[] toWriteSmall2Bytes = toWriteSmall2.getBytes();
|
||||
assertTrue("First element should be in the list",
|
||||
buffer.contains(toWriteSmall1Bytes));
|
||||
mBuffer.contains(toWriteSmall1Bytes));
|
||||
assertTrue("Second element should be in the list",
|
||||
buffer.contains(toWriteSmall2Bytes));
|
||||
mBuffer.contains(toWriteSmall2Bytes));
|
||||
|
||||
buffer.add(toWriteBig);
|
||||
mBuffer.add(toWriteBig);
|
||||
byte[] toWriteBigBytes = toWriteBig.getBytes();
|
||||
assertTrue("Third element should overwrite all others",
|
||||
!buffer.contains(toWriteSmall1Bytes));
|
||||
!mBuffer.contains(toWriteSmall1Bytes));
|
||||
assertTrue("Third element should overwrite all others",
|
||||
!buffer.contains(toWriteSmall2Bytes));
|
||||
!mBuffer.contains(toWriteSmall2Bytes));
|
||||
assertTrue("Third element should overwrite all others",
|
||||
buffer.contains(toWriteBigBytes));
|
||||
mBuffer.contains(toWriteBigBytes));
|
||||
|
||||
assertEquals(" Buffer should have only 1 big element", buffer.mBuffer.size(), 1);
|
||||
assertEquals(" Buffer should have only 1 big element", 1, mBuffer.size());
|
||||
assertEquals(String.format(" Buffer is full, used space should be %d", bufferCapacity),
|
||||
buffer.mBufferSize, bufferCapacity);
|
||||
assertEquals(" Buffer is full, available space should be 0",
|
||||
buffer.getAvailableSpace(), 0);
|
||||
mBuffer.getBufferSize(), bufferCapacity);
|
||||
assertEquals(" Buffer is full, available space should be 0", 0,
|
||||
mBuffer.getAvailableSpace());
|
||||
}
|
||||
|
||||
private WindowTraceBuffer buildRingBuffer(int capacity) throws IOException {
|
||||
return new WindowTraceBuffer.Builder()
|
||||
.setContinuousMode(true)
|
||||
.setBufferCapacity(capacity)
|
||||
.setTraceFile(mFile)
|
||||
.build();
|
||||
@Test
|
||||
public void test_startResetsBuffer() {
|
||||
ProtoOutputStream toWrite = getDummy(1);
|
||||
mBuffer.resetBuffer();
|
||||
Preconditions.checkArgument(mBuffer.size() == 0);
|
||||
|
||||
mBuffer.add(toWrite);
|
||||
assertEquals("Item was not added to the buffer", 1, mBuffer.size());
|
||||
mBuffer.resetBuffer();
|
||||
assertEquals("Buffer should be empty after reset", 0, mBuffer.size());
|
||||
assertEquals("Buffer size should be 0 after reset", 0, mBuffer.getBufferSize());
|
||||
}
|
||||
|
||||
private ProtoOutputStream getDummy(int value) {
|
||||
@@ -212,7 +181,4 @@ public class WindowTraceBufferTest {
|
||||
return toWrite;
|
||||
}
|
||||
|
||||
private WindowTraceBuffer buildQueueBuffer(int size) throws IOException {
|
||||
return new WindowTraceQueueBuffer(size, mFile, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,7 @@ public class WindowTracingTest {
|
||||
mFile.delete();
|
||||
|
||||
mWindowTracing = new WindowTracing(mFile, mWmMock, mChoreographer,
|
||||
new WindowManagerGlobalLock());
|
||||
mWindowTracing.setContinuousMode(false /* continuous */, null /* pw */);
|
||||
new WindowManagerGlobalLock(), 1024);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -103,13 +102,13 @@ public class WindowTracingTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEnabled_returnsTrueAfterStart() throws Exception {
|
||||
public void isEnabled_returnsTrueAfterStart() {
|
||||
mWindowTracing.startTrace(mock(PrintWriter.class));
|
||||
assertTrue(mWindowTracing.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEnabled_returnsFalseAfterStop() throws Exception {
|
||||
public void isEnabled_returnsFalseAfterStop() {
|
||||
mWindowTracing.startTrace(mock(PrintWriter.class));
|
||||
mWindowTracing.stopTrace(mock(PrintWriter.class));
|
||||
assertFalse(mWindowTracing.isEnabled());
|
||||
@@ -133,6 +132,8 @@ public class WindowTracingTest {
|
||||
mWindowTracing.startTrace(mock(PrintWriter.class));
|
||||
mWindowTracing.stopTrace(mock(PrintWriter.class));
|
||||
|
||||
assertTrue("Trace file should exist", mFile.exists());
|
||||
|
||||
byte[] header = new byte[MAGIC_HEADER.length];
|
||||
try (InputStream is = new FileInputStream(mFile)) {
|
||||
assertEquals(MAGIC_HEADER.length, is.read(header));
|
||||
|
||||
Reference in New Issue
Block a user