Loading apex/statsd/framework/java/android/util/StatsEvent.java +42 −8 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -619,6 +623,7 @@ public final class StatsEvent { @NonNull public Builder usePooledBuffer() { mUsePooledBuffer = true; mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos); return this; } Loading Loading @@ -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() { Loading @@ -717,15 +723,26 @@ public final class StatsEvent { } private void release() { // 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() { Loading @@ -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; } // 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; } return result; mBytes = Arrays.copyOf(mBytes, newSize); } return true; } /** Loading apex/statsd/framework/test/src/android/util/StatsEventTest.java +160 −0 Original line number Diff line number Diff line Loading @@ -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}. Loading Loading @@ -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]; Loading Loading
apex/statsd/framework/java/android/util/StatsEvent.java +42 −8 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -619,6 +623,7 @@ public final class StatsEvent { @NonNull public Builder usePooledBuffer() { mUsePooledBuffer = true; mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos); return this; } Loading Loading @@ -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() { Loading @@ -717,15 +723,26 @@ public final class StatsEvent { } private void release() { // 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() { Loading @@ -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; } // 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; } return result; mBytes = Arrays.copyOf(mBytes, newSize); } return true; } /** Loading
apex/statsd/framework/test/src/android/util/StatsEventTest.java +160 −0 Original line number Diff line number Diff line Loading @@ -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}. Loading Loading @@ -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]; Loading