Support a ring buffer or a queue to store window manager traces
Currently the WM uses a BlockingQueue with limited capacity to store
the traces and a separate thread to continuously write the buffer
elements to disk. This implementation allows the WM trace to use its
current logging behavior or to use a ring buffer instead.
The ring buffer:
- Stores the most recent traces and discard the oldest ones when it
reaches maximum capacity.
- Write to disk only when the trace logging is stopped or during a
bugreport, instead of having a thread to continuously write to disk.
The ring buffer can be selected (or disabled) using the command:
- adb shell cmd window tracing continuous (true/false)
Any value other than "true" (case insensitive) is considered false
After selecting ring buffer, start and stop WM trace
monitoring with 'adb shell cmd window tracing (start/stop)'
Current capacity is fixed to 512KB. Future implementations will make
this value configurable.
Test: Flash a device. Enable continuous mode and window manager
tracing. Use the device. Stop the trace and pull the logged trace.
Open the trace in Winscope and check if the last X KB of data are
there. Unit tests: atest WmTests:WindowTraceBufferTest
Change-Id: Id4108b0231ab7af504240ea35c8a030fecf8b8a9
diff --git a/services/core/java/com/android/server/wm/WindowTraceBuffer.java b/services/core/java/com/android/server/wm/WindowTraceBuffer.java
index 936ee85..2f672f2 100644
--- a/services/core/java/com/android/server/wm/WindowTraceBuffer.java
+++ b/services/core/java/com/android/server/wm/WindowTraceBuffer.java
@@ -23,12 +23,15 @@
import android.os.Trace;
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;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Queue;
/**
* Buffer used for window tracing.
@@ -36,16 +39,15 @@
abstract class WindowTraceBuffer {
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
- final Object mBufferSizeLock = new Object();
- final BlockingQueue<byte[]> mBuffer;
+ final Object mBufferLock = new Object();
+ final Queue<byte[]> mBuffer = new ArrayDeque<>();
+ final File mTraceFile;
int mBufferSize;
private final int mBufferCapacity;
- private final File mTraceFile;
WindowTraceBuffer(int size, File traceFile) throws IOException {
mBufferCapacity = size;
mTraceFile = traceFile;
- mBuffer = new LinkedBlockingQueue<>();
initTraceFile();
}
@@ -57,65 +59,45 @@
/**
* Inserts the specified element into this buffer.
*
- * This method is synchronized with {@code #take()} and {@code #clear()}
- * for consistency.
- *
* @param proto the element to add
- * @return {@code true} if the inserted item was inserted into the buffer
* @throws IllegalStateException if the element cannot be added because it is larger
* than the buffer size.
*/
- boolean add(ProtoOutputStream proto) throws InterruptedException {
+ void add(ProtoOutputStream proto) {
byte[] protoBytes = proto.getBytes();
int protoLength = protoBytes.length;
if (protoLength > mBufferCapacity) {
throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
+ mBufferCapacity + " Object size: " + protoLength);
}
- synchronized (mBufferSizeLock) {
- boolean canAdd = canAdd(protoBytes);
+ synchronized (mBufferLock) {
+ boolean canAdd = canAdd(protoLength);
if (canAdd) {
mBuffer.offer(protoBytes);
mBufferSize += protoLength;
}
- return canAdd;
+ mBufferLock.notify();
}
}
- void writeNextBufferElementToFile() throws IOException {
- byte[] proto;
+ /**
+ * 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 {
- proto = take();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return;
- }
-
- try {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
- try (OutputStream os = new FileOutputStream(mTraceFile, true)) {
- os.write(proto);
- }
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFile");
+ writeTraceToFile();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- /**
- * Retrieves and removes the head of this queue, waiting if necessary
- * until an element becomes available.
- *
- * This method is synchronized with {@code #add(ProtoOutputStream)} and {@code #clear()}
- * for consistency.
- *
- * @return the head of this buffer, or {@code null} if this buffer is empty
- */
- private byte[] take() throws InterruptedException {
- byte[] item = mBuffer.take();
- synchronized (mBufferSizeLock) {
- mBufferSize -= item.length;
- return item;
- }
+ @VisibleForTesting
+ boolean contains(byte[] other) {
+ return mBuffer.stream()
+ .anyMatch(p -> Arrays.equals(p, other));
}
private void initTraceFile() throws IOException {
@@ -132,25 +114,31 @@
* Checks if the element can be added to the buffer. The element is already certain to be
* smaller than the overall buffer size.
*
- * @param protoBytes byte array representation of the Proto object to add
- * @return <tt>true<</tt> if the element can be added to the buffer or not
+ * @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(byte[] protoBytes) throws InterruptedException;
+ abstract boolean canAdd(int protoLength);
/**
* Flush all buffer content to the disk.
*
* @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile}
*/
- abstract void writeToDisk() throws IOException, InterruptedException;
+ abstract void writeTraceToFile() throws IOException, InterruptedException;
/**
- * Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceQueueBuffer}
+ * 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;
+ }
Builder setTraceFile(File traceFile) {
mTraceFile = traceFile;
@@ -175,7 +163,11 @@
throw new IllegalArgumentException("A valid trace file must be specified.");
}
- return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile);
+ if (mContinuous) {
+ return new WindowTraceRingBuffer(mBufferCapacity, mTraceFile);
+ } else {
+ return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile);
+ }
}
}
}