diff options
| -rw-r--r-- | apex/statsd/framework/java/android/util/StatsEvent.java | 50 | ||||
| -rw-r--r-- | apex/statsd/framework/test/src/android/util/StatsEventTest.java | 160 |
2 files changed, 202 insertions, 8 deletions
diff --git a/apex/statsd/framework/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java index 8bd36a516b12..8be5c63f31e3 100644 --- a/apex/statsd/framework/java/android/util/StatsEvent.java +++ b/apex/statsd/framework/java/android/util/StatsEvent.java @@ -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; } /** diff --git a/apex/statsd/framework/test/src/android/util/StatsEventTest.java b/apex/statsd/framework/test/src/android/util/StatsEventTest.java index 7b511553a26f..8d263699d9c8 100644 --- a/apex/statsd/framework/test/src/android/util/StatsEventTest.java +++ b/apex/statsd/framework/test/src/android/util/StatsEventTest.java @@ -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]; |