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.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StatsEvent builds and stores the buffer sent over the statsd socket.
|
* StatsEvent builds and stores the buffer sent over the statsd socket.
|
||||||
* This class defines and encapsulates the socket protocol.
|
* 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.
|
// Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag.
|
||||||
// See android_util_StatsLog.cpp.
|
// 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 int mAtomId;
|
||||||
private final byte[] mPayload;
|
private final byte[] mPayload;
|
||||||
@@ -619,6 +623,7 @@ public final class StatsEvent {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public Builder usePooledBuffer() {
|
public Builder usePooledBuffer() {
|
||||||
mUsePooledBuffer = true;
|
mUsePooledBuffer = true;
|
||||||
|
mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,8 +699,9 @@ public final class StatsEvent {
|
|||||||
@GuardedBy("sLock")
|
@GuardedBy("sLock")
|
||||||
private static Buffer sPool;
|
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 boolean mOverflow = false;
|
||||||
|
private int mMaxSize = MAX_PULL_PAYLOAD_SIZE;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private static Buffer obtain() {
|
private static Buffer obtain() {
|
||||||
@@ -717,15 +723,26 @@ public final class StatsEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void release() {
|
private void release() {
|
||||||
synchronized (sLock) {
|
// Recycle this Buffer if its size is MAX_PUSH_PAYLOAD_SIZE or under.
|
||||||
if (null == sPool) {
|
if (mBytes.length <= MAX_PUSH_PAYLOAD_SIZE) {
|
||||||
sPool = this;
|
synchronized (sLock) {
|
||||||
|
if (null == sPool) {
|
||||||
|
sPool = this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
mOverflow = false;
|
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() {
|
private boolean hasOverflowed() {
|
||||||
@@ -740,11 +757,28 @@ public final class StatsEvent {
|
|||||||
* @return true if space is available, false otherwise.
|
* @return true if space is available, false otherwise.
|
||||||
**/
|
**/
|
||||||
private boolean hasEnoughSpace(final int index, final int numBytes) {
|
private boolean hasEnoughSpace(final int index, final int numBytes) {
|
||||||
final boolean result = index + numBytes < MAX_PAYLOAD_SIZE;
|
final int totalBytesNeeded = index + numBytes;
|
||||||
if (!result) {
|
|
||||||
|
if (totalBytesNeeded > mMaxSize) {
|
||||||
mOverflow = true;
|
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.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal tests for {@link StatsEvent}.
|
* Internal tests for {@link StatsEvent}.
|
||||||
@@ -644,6 +645,165 @@ public class StatsEventTest {
|
|||||||
statsEvent.release();
|
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) {
|
private static byte[] getByteArrayFromByteBuffer(final ByteBuffer buffer) {
|
||||||
final int numBytes = buffer.getInt();
|
final int numBytes = buffer.getInt();
|
||||||
byte[] bytes = new byte[numBytes];
|
byte[] bytes = new byte[numBytes];
|
||||||
|
|||||||
Reference in New Issue
Block a user