Merge "Increase size limit for pulled StatsEvent." into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4c21a3b737
@@ -26,6 +26,8 @@ import android.os.SystemClock;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* StatsEvent builds and stores the buffer sent over the statsd socket.
|
||||
* This class defines and encapsulates the socket protocol.
|
||||
@@ -224,7 +226,9 @@ public final class StatsEvent {
|
||||
|
||||
// Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag.
|
||||
// See android_util_StatsLog.cpp.
|
||||
private static final int MAX_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
|
||||
private static final int MAX_PUSH_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
|
||||
|
||||
private static final int MAX_PULL_PAYLOAD_SIZE = 50 * 1024; // 50 KB
|
||||
|
||||
private final int mAtomId;
|
||||
private final byte[] mPayload;
|
||||
@@ -619,6 +623,7 @@ public final class StatsEvent {
|
||||
@NonNull
|
||||
public Builder usePooledBuffer() {
|
||||
mUsePooledBuffer = true;
|
||||
mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -694,8 +699,9 @@ public final class StatsEvent {
|
||||
@GuardedBy("sLock")
|
||||
private static Buffer sPool;
|
||||
|
||||
private final byte[] mBytes = new byte[MAX_PAYLOAD_SIZE];
|
||||
private byte[] mBytes = new byte[MAX_PUSH_PAYLOAD_SIZE];
|
||||
private boolean mOverflow = false;
|
||||
private int mMaxSize = MAX_PULL_PAYLOAD_SIZE;
|
||||
|
||||
@NonNull
|
||||
private static Buffer obtain() {
|
||||
@@ -717,15 +723,26 @@ public final class StatsEvent {
|
||||
}
|
||||
|
||||
private void release() {
|
||||
synchronized (sLock) {
|
||||
if (null == sPool) {
|
||||
sPool = this;
|
||||
// Recycle this Buffer if its size is MAX_PUSH_PAYLOAD_SIZE or under.
|
||||
if (mBytes.length <= MAX_PUSH_PAYLOAD_SIZE) {
|
||||
synchronized (sLock) {
|
||||
if (null == sPool) {
|
||||
sPool = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
mOverflow = false;
|
||||
mMaxSize = MAX_PULL_PAYLOAD_SIZE;
|
||||
}
|
||||
|
||||
private void setMaxSize(final int maxSize, final int numBytesWritten) {
|
||||
mMaxSize = maxSize;
|
||||
if (numBytesWritten > maxSize) {
|
||||
mOverflow = true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasOverflowed() {
|
||||
@@ -740,11 +757,28 @@ public final class StatsEvent {
|
||||
* @return true if space is available, false otherwise.
|
||||
**/
|
||||
private boolean hasEnoughSpace(final int index, final int numBytes) {
|
||||
final boolean result = index + numBytes < MAX_PAYLOAD_SIZE;
|
||||
if (!result) {
|
||||
final int totalBytesNeeded = index + numBytes;
|
||||
|
||||
if (totalBytesNeeded > mMaxSize) {
|
||||
mOverflow = true;
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
|
||||
// Expand buffer if needed.
|
||||
if (mBytes.length < mMaxSize && totalBytesNeeded > mBytes.length) {
|
||||
int newSize = mBytes.length;
|
||||
do {
|
||||
newSize *= 2;
|
||||
} while (newSize <= totalBytesNeeded);
|
||||
|
||||
if (newSize > mMaxSize) {
|
||||
newSize = mMaxSize;
|
||||
}
|
||||
|
||||
mBytes = Arrays.copyOf(mBytes, newSize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Internal tests for {@link StatsEvent}.
|
||||
@@ -644,6 +645,165 @@ public class StatsEventTest {
|
||||
statsEvent.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLargePulledEvent() {
|
||||
final int expectedAtomId = 10_020;
|
||||
byte[] field1 = new byte[10 * 1024];
|
||||
new Random().nextBytes(field1);
|
||||
|
||||
final long minTimestamp = SystemClock.elapsedRealtimeNanos();
|
||||
final StatsEvent statsEvent =
|
||||
StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).build();
|
||||
final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
|
||||
|
||||
assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
|
||||
|
||||
final ByteBuffer buffer =
|
||||
ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
assertWithMessage("Root element in buffer is not TYPE_OBJECT")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_OBJECT);
|
||||
|
||||
assertWithMessage("Incorrect number of elements in root object")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(3);
|
||||
|
||||
assertWithMessage("First element is not timestamp")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_LONG);
|
||||
|
||||
assertWithMessage("Incorrect timestamp")
|
||||
.that(buffer.getLong())
|
||||
.isIn(Range.closed(minTimestamp, maxTimestamp));
|
||||
|
||||
assertWithMessage("Second element is not atom id")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_INT);
|
||||
|
||||
assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId);
|
||||
|
||||
assertWithMessage("Third element is not byte array")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_BYTE_ARRAY);
|
||||
|
||||
final byte[] field1Actual = getByteArrayFromByteBuffer(buffer);
|
||||
assertWithMessage("Incorrect field 1").that(field1Actual).isEqualTo(field1);
|
||||
|
||||
assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
|
||||
|
||||
statsEvent.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPulledEventOverflow() {
|
||||
final int expectedAtomId = 10_020;
|
||||
byte[] field1 = new byte[50 * 1024];
|
||||
new Random().nextBytes(field1);
|
||||
|
||||
final long minTimestamp = SystemClock.elapsedRealtimeNanos();
|
||||
final StatsEvent statsEvent =
|
||||
StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).build();
|
||||
final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
|
||||
|
||||
assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
|
||||
|
||||
final ByteBuffer buffer =
|
||||
ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
assertWithMessage("Root element in buffer is not TYPE_OBJECT")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_OBJECT);
|
||||
|
||||
assertWithMessage("Incorrect number of elements in root object")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(3);
|
||||
|
||||
assertWithMessage("First element is not timestamp")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_LONG);
|
||||
|
||||
assertWithMessage("Incorrect timestamp")
|
||||
.that(buffer.getLong())
|
||||
.isIn(Range.closed(minTimestamp, maxTimestamp));
|
||||
|
||||
assertWithMessage("Second element is not atom id")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_INT);
|
||||
|
||||
assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId);
|
||||
|
||||
assertWithMessage("Third element is not errors type")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_ERRORS);
|
||||
|
||||
final int errorMask = buffer.getInt();
|
||||
|
||||
assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask")
|
||||
.that(errorMask)
|
||||
.isEqualTo(StatsEvent.ERROR_OVERFLOW);
|
||||
|
||||
assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
|
||||
|
||||
statsEvent.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushedEventOverflow() {
|
||||
final int expectedAtomId = 10_020;
|
||||
byte[] field1 = new byte[10 * 1024];
|
||||
new Random().nextBytes(field1);
|
||||
|
||||
final long minTimestamp = SystemClock.elapsedRealtimeNanos();
|
||||
final StatsEvent statsEvent = StatsEvent.newBuilder()
|
||||
.setAtomId(expectedAtomId)
|
||||
.writeByteArray(field1)
|
||||
.usePooledBuffer()
|
||||
.build();
|
||||
final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
|
||||
|
||||
assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
|
||||
|
||||
final ByteBuffer buffer =
|
||||
ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
assertWithMessage("Root element in buffer is not TYPE_OBJECT")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_OBJECT);
|
||||
|
||||
assertWithMessage("Incorrect number of elements in root object")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(3);
|
||||
|
||||
assertWithMessage("First element is not timestamp")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_LONG);
|
||||
|
||||
assertWithMessage("Incorrect timestamp")
|
||||
.that(buffer.getLong())
|
||||
.isIn(Range.closed(minTimestamp, maxTimestamp));
|
||||
|
||||
assertWithMessage("Second element is not atom id")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_INT);
|
||||
|
||||
assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId);
|
||||
|
||||
assertWithMessage("Third element is not errors type")
|
||||
.that(buffer.get())
|
||||
.isEqualTo(StatsEvent.TYPE_ERRORS);
|
||||
|
||||
final int errorMask = buffer.getInt();
|
||||
|
||||
assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask")
|
||||
.that(errorMask)
|
||||
.isEqualTo(StatsEvent.ERROR_OVERFLOW);
|
||||
|
||||
assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
|
||||
|
||||
statsEvent.release();
|
||||
}
|
||||
|
||||
private static byte[] getByteArrayFromByteBuffer(final ByteBuffer buffer) {
|
||||
final int numBytes = buffer.getInt();
|
||||
byte[] bytes = new byte[numBytes];
|
||||
|
||||
Reference in New Issue
Block a user