summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowTraceBuffer.java88
-rw-r--r--services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java68
-rw-r--r--services/core/java/com/android/server/wm/WindowTraceRingBuffer.java68
-rw-r--r--services/core/java/com/android/server/wm/WindowTracing.java73
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java124
7 files changed, 326 insertions, 104 deletions
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8373b44112fe..752c24e7edb6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6056,6 +6056,7 @@ public class WindowManagerService extends IWindowManager.Stub
pw.println(" d[isplays]: active display contents");
pw.println(" t[okens]: token list");
pw.println(" w[indows]: window list");
+ pw.println(" trace: write Winscope trace to file");
pw.println(" cmd may also be a NAME to dump windows. NAME may");
pw.println(" be a partial substring in a window name, a");
pw.println(" Window hex object identifier, or");
@@ -6129,6 +6130,11 @@ public class WindowManagerService extends IWindowManager.Stub
mRoot.forAllWindows(w -> {pw.println(w);}, true /* traverseTopToBottom */);
}
return;
+ } else if ("trace".equals(cmd)) {
+ synchronized (mGlobalLock) {
+ mWindowTracing.writeTraceToFile();
+ }
+ return;
} else {
// Dumping a single name?
if (!dumpWindows(pw, cmd, args, opti, dumpAll)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 6865ce305442..83e3c71cbee3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -72,8 +72,7 @@ public class WindowManagerShellCommand extends ShellCommand {
// XXX this should probably be changed to use openFileForSystem() to create
// the output trace file, so the shell gets the correct semantics for where
// trace files can be written.
- return mInternal.mWindowTracing.onShellCommand(this,
- getNextArgRequired());
+ return mInternal.mWindowTracing.onShellCommand(this);
case "set-user-rotation":
return runSetDisplayUserRotation(pw);
case "set-fix-to-user-rotation":
diff --git a/services/core/java/com/android/server/wm/WindowTraceBuffer.java b/services/core/java/com/android/server/wm/WindowTraceBuffer.java
index 936ee85697b8..2f672f24cc4f 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 static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
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 @@ import java.util.concurrent.LinkedBlockingQueue;
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 @@ abstract class WindowTraceBuffer {
/**
* 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;
- try {
- proto = take();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return;
- }
-
+ /**
+ * 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, "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 @@ abstract class WindowTraceBuffer {
* 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 @@ abstract class WindowTraceBuffer {
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);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java b/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java
index b7fc7ac8cb5e..eaedde9ea842 100644
--- a/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java
+++ b/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java
@@ -18,10 +18,14 @@ package com.android.server.wm;
import static android.os.Build.IS_USER;
+import android.util.Log;
+
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
@@ -29,14 +33,17 @@ import java.io.IOException;
* Once the buffer is full it will no longer accepts new elements.
*/
class WindowTraceQueueBuffer extends WindowTraceBuffer {
- private Thread mWriterThread;
+ private static final String TAG = "WindowTracing";
+
+ private Thread mConsumerThread;
private boolean mCancel;
@VisibleForTesting
- WindowTraceQueueBuffer(int size, File traceFile, boolean startWriterThread) throws IOException {
+ WindowTraceQueueBuffer(int size, File traceFile, boolean startConsumerThread)
+ throws IOException {
super(size, traceFile);
- if (startWriterThread) {
- initializeWriterThread();
+ if (startConsumerThread) {
+ initializeConsumerThread();
}
}
@@ -44,45 +51,56 @@ class WindowTraceQueueBuffer extends WindowTraceBuffer {
this(size, traceFile, !IS_USER);
}
- private void initializeWriterThread() {
+ private void initializeConsumerThread() {
mCancel = false;
- mWriterThread = new Thread(() -> {
+ mConsumerThread = new Thread(() -> {
try {
loop();
+ } catch (InterruptedException e) {
+ Log.i(TAG, "Interrupting trace consumer thread");
} catch (IOException e) {
- throw new IllegalStateException("Failed to execute trace write loop thread", e);
+ Log.e(TAG, "Failed to execute trace consumer thread", e);
}
}, "window_tracing");
- mWriterThread.start();
+ mConsumerThread.start();
}
- private void loop() throws IOException {
+ private void loop() throws IOException, InterruptedException {
while (!mCancel) {
- writeNextBufferElementToFile();
- }
- }
+ byte[] proto;
+ synchronized (mBufferLock) {
+ mBufferLock.wait();
- private void restartWriterThread() throws InterruptedException {
- if (mWriterThread != null) {
- mCancel = true;
- mWriterThread.interrupt();
- mWriterThread.join();
- initializeWriterThread();
+ proto = mBuffer.poll();
+ if (proto != null) {
+ mBufferSize -= proto.length;
+ }
+ }
+
+ if (proto != null) {
+ try (OutputStream os = new FileOutputStream(mTraceFile, true)) {
+ os.write(proto);
+ }
+ }
}
}
@Override
- boolean canAdd(byte[] protoBytes) {
+ boolean canAdd(int protoLength) {
long availableSpace = getAvailableSpace();
- return availableSpace >= protoBytes.length;
+ return availableSpace >= protoLength;
}
@Override
- void writeToDisk() throws InterruptedException {
- while (!mBuffer.isEmpty()) {
- mBufferSizeLock.wait();
- mBufferSizeLock.notify();
+ void writeTraceToFile() throws InterruptedException {
+ synchronized (mBufferLock) {
+ mCancel = true;
+ mBufferLock.notify();
+ }
+
+ if (mConsumerThread != null) {
+ mConsumerThread.join();
+ mConsumerThread = null;
}
- restartWriterThread();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowTraceRingBuffer.java b/services/core/java/com/android/server/wm/WindowTraceRingBuffer.java
new file mode 100644
index 000000000000..7c69f2368845
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTraceRingBuffer.java
@@ -0,0 +1,68 @@
+/*
+ * 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 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()) {
+ byte[] proto;
+ proto = mBuffer.poll();
+ mBufferSize -= proto.length;
+ os.write(proto);
+ }
+ }
+ }
+ }
+
+ private void discardOldest() {
+ byte[] item = mBuffer.poll();
+ if (item == null) {
+ throw new IllegalStateException("No element to discard from buffer");
+ }
+ mBufferSize -= item.length;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 63539c4f9fd9..4c9a917c9248 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -53,6 +53,7 @@ class WindowTracing {
private WindowTraceBuffer mTraceBuffer;
+ private boolean mContinuousMode;
private boolean mEnabled;
private volatile boolean mEnabledLockFree;
@@ -70,13 +71,11 @@ class WindowTracing {
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.");
- }
+ writeTraceToFileLocked();
}
- mTraceBuffer = mBufferBuilder.build();
+ mTraceBuffer = mBufferBuilder
+ .setContinuousMode(mContinuousMode)
+ .build();
mEnabled = mEnabledLockFree = true;
}
}
@@ -104,29 +103,29 @@ class WindowTracing {
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);
- }
+ writeTraceToFileLocked();
+ mTraceBuffer = null;
}
logAndPrintln(pw, "Trace written to " + mBufferBuilder.getFile() + ".");
}
}
+ private void setContinuousMode(boolean continuous, PrintWriter pw) {
+ logAndPrintln(pw, "Setting window tracing continuous mode to " + continuous);
+
+ if (mEnabled) {
+ logAndPrintln(pw, "Trace is currently active, change will take effect once the "
+ + "trace is restarted.");
+ }
+ mContinuousMode = continuous;
+ }
+
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();
- }
+ mTraceBuffer.add(proto);
}
boolean isEnabled() {
@@ -138,9 +137,10 @@ class WindowTracing {
return new WindowTracing(file);
}
- int onShellCommand(ShellCommand shell, String cmd) {
+ int onShellCommand(ShellCommand shell) {
PrintWriter pw = shell.getOutPrintWriter();
try {
+ String cmd = shell.getNextArgRequired();
switch (cmd) {
case "start":
startTrace(pw);
@@ -148,6 +148,9 @@ class WindowTracing {
case "stop":
stopTrace(pw);
return 0;
+ case "continuous":
+ setContinuousMode(Boolean.valueOf(shell.getNextArgRequired()), pw);
+ return 0;
default:
pw.println("Unknown command: " + cmd);
return -1;
@@ -162,6 +165,7 @@ class WindowTracing {
if (!isEnabled()) {
return;
}
+
ProtoOutputStream os = new ProtoOutputStream();
long tokenOuter = os.start(ENTRY);
os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
@@ -179,4 +183,33 @@ class WindowTracing {
appendTraceEntry(os);
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
+
+ /**
+ * 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.
+ * This method is synchronized with {@code #startTrace(PrintWriter)} and
+ * {@link #stopTrace(PrintWriter)}.
+ */
+ void writeTraceToFile() {
+ synchronized (mLock) {
+ writeTraceToFileLocked();
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
index 8d834974148c..df3ef551d5a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
@@ -19,8 +19,6 @@ package com.android.server.wm;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -31,13 +29,14 @@ import android.util.proto.ProtoOutputStream;
import androidx.test.filters.SmallTest;
+import com.android.internal.util.Preconditions;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
-import java.util.Arrays;
/**
@@ -49,8 +48,6 @@ import java.util.Arrays;
@SmallTest
@Presubmit
public class WindowTraceBufferTest {
- private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
private File mFile;
@Before
@@ -81,18 +78,22 @@ public class WindowTraceBufferTest {
buffer.add(toWrite1);
assertTrue("First element should be in the list",
- buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes)));
+ buffer.contains(toWrite1Bytes));
buffer.add(toWrite2);
assertTrue("First element should be in the list",
- buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes)));
+ buffer.contains(toWrite1Bytes));
assertTrue("Second element should be in the list",
- buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite2Bytes)));
+ buffer.contains(toWrite2Bytes));
buffer.add(toWrite3);
+ 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.mBuffer.stream().noneMatch(p -> Arrays.equals(p, toWrite3Bytes)));
+ !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),
@@ -101,6 +102,111 @@ public class WindowTraceBufferTest {
buffer.getAvailableSpace(), 0);
}
+ @Test
+ public void testTraceRingBuffer_addItem() throws Exception {
+ ProtoOutputStream toWrite = getDummy(1);
+ final int objectSize = toWrite.getBytes().length;
+
+ final WindowTraceBuffer buffer = buildRingBuffer(objectSize);
+
+ Preconditions.checkArgument(buffer.mBuffer.isEmpty());
+
+ buffer.add(toWrite);
+
+ assertEquals("Item was not added to the buffer", buffer.mBuffer.size(), 1);
+ assertEquals("Total buffer getSize differs from inserted object",
+ buffer.mBufferSize, objectSize);
+ assertEquals("Available buffer space does not match used one",
+ buffer.getAvailableSpace(), 0);
+ }
+
+ @Test
+ public void testTraceRingBuffer_addItemMustOverwriteOne() throws Exception {
+ ProtoOutputStream toWrite1 = getDummy(1);
+ ProtoOutputStream toWrite2 = getDummy(2);
+ ProtoOutputStream toWrite3 = getDummy(3);
+ byte[] toWrite1Bytes = toWrite1.getBytes();
+ byte[] toWrite2Bytes = toWrite2.getBytes();
+ byte[] toWrite3Bytes = toWrite3.getBytes();
+ final int objectSize = toWrite1.getBytes().length;
+
+ final int bufferCapacity = objectSize * 2 + 1;
+ final WindowTraceBuffer buffer = buildRingBuffer(bufferCapacity);
+
+ buffer.add(toWrite1);
+ assertTrue("First element should be in the list",
+ buffer.contains(toWrite1Bytes));
+
+ buffer.add(toWrite2);
+ 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);
+ assertTrue("First element should not be in the list",
+ !buffer.contains(toWrite1Bytes));
+ assertTrue("Second element should be in the list",
+ buffer.contains(toWrite2Bytes));
+ assertTrue("Third element should 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 - 1);
+ assertEquals(" Buffer is full, available space should be 0",
+ buffer.getAvailableSpace(), 1);
+ }
+
+ @Test
+ public void testTraceRingBuffer_addItemMustOverwriteMultiple() throws Exception {
+ ProtoOutputStream toWriteSmall1 = getDummy(1);
+ ProtoOutputStream toWriteSmall2 = getDummy(2);
+ byte[] toWriteSmall1Bytes = toWriteSmall1.getBytes();
+ byte[] toWriteSmall2Bytes = toWriteSmall2.getBytes();
+ final int objectSize = toWriteSmall1.getBytes().length;
+
+ final int bufferCapacity = objectSize * 2;
+ final WindowTraceBuffer buffer = buildRingBuffer(bufferCapacity);
+
+ ProtoOutputStream toWriteBig = new ProtoOutputStream();
+ toWriteBig.write(MAGIC_NUMBER, 1);
+ toWriteBig.write(MAGIC_NUMBER, 2);
+ byte[] toWriteBigBytes = toWriteBig.getBytes();
+ toWriteBig.flush();
+
+ buffer.add(toWriteSmall1);
+ assertTrue("First element should be in the list",
+ buffer.contains(toWriteSmall1Bytes));
+
+ buffer.add(toWriteSmall2);
+ assertTrue("First element should be in the list",
+ buffer.contains(toWriteSmall1Bytes));
+ assertTrue("Second element should be in the list",
+ buffer.contains(toWriteSmall2Bytes));
+
+ buffer.add(toWriteBig);
+ assertTrue("Third element should overwrite all others",
+ !buffer.contains(toWriteSmall1Bytes));
+ assertTrue("Third element should overwrite all others",
+ !buffer.contains(toWriteSmall2Bytes));
+ assertTrue("Third element should overwrite all others",
+ buffer.contains(toWriteBigBytes));
+
+ assertEquals(" Buffer should have only 1 big element", buffer.mBuffer.size(), 1);
+ 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);
+ }
+
+ private WindowTraceBuffer buildRingBuffer(int capacity) throws IOException {
+ return new WindowTraceBuffer.Builder()
+ .setContinuousMode(true)
+ .setBufferCapacity(capacity)
+ .setTraceFile(mFile)
+ .build();
+ }
+
private ProtoOutputStream getDummy(int value) {
ProtoOutputStream toWrite = new ProtoOutputStream();
toWrite.write(MAGIC_NUMBER, value);