Merge "Support a ring buffer or a queue to store window manager traces"
This commit is contained in:
committed by
Android (Google) Code Review
commit
f43f624efb
@@ -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)) {
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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;
|
||||
/**
|
||||
* 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 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user