Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 351e40fb authored by Muhammad Qureshi's avatar Muhammad Qureshi
Browse files

Increase size limit for pulled StatsEvent.

Increase StatsEvent max byte size to 50 KB when not using pooled buffer.

- Set the default max size to 50 KB
- The starting buffer size is still ~4 KB for pushed and pulled events.
- If a write would exceed the buffer bounds, double buffer size until
write fits or 50 KB limit is exceeded in which case the overflow bit is set to true.
- If usePooledBuffer() is called, max size is set to ~4 KB but buffer
isn't resized. And if the current payload exceeds this limit, set
overflow bit to true.
- Only "recycle" Buffer object if its size is <= ~4 KB.

Bug: 158214941
Test: atest FrameworkStatsdTest
Change-Id: I9b6f73688b0bccb5a70f4ef51750464ec9c87fb9
parent 8cde2b1a
Loading
Loading
Loading
Loading
+42 −8
Original line number Diff line number Diff line
@@ -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() {
            // 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;
            }

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

        /**
+160 −0
Original line number Diff line number Diff line
@@ -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];