Merge "Remove on-disk WM trace logging"

This commit is contained in:
Nataniel Borges
2019-02-15 21:02:46 +00:00
committed by Android (Google) Code Review
7 changed files with 307 additions and 475 deletions

View File

@@ -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

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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));