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

Commit 4549e734 authored by Muhammad Qureshi's avatar Muhammad Qureshi
Browse files

Allow annotations to be added to Atom Id

Add atom-level annotations support in StatsEvent.java

setAtomId must be called right after StatsEvent.newBuilder().
Calling setAtomId out of order results in
ERROR_ATOM_ID_INVALID_POSITION.

Bug: 151158793
Test: atest FrameworkStatsdTest
Change-Id: Ib574f079227660193e12b3aa37136d36e154479a
parent ffd92af9
Loading
Loading
Loading
Loading
+35 −13
Original line number Diff line number Diff line
@@ -188,6 +188,12 @@ public final class StatsEvent {
    @VisibleForTesting
    public static final int ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL = 0x1000;

    /**
     * @hide
     **/
    @VisibleForTesting
    public static final int ERROR_ATOM_ID_INVALID_POSITION = 0x2000;

    // Size limits.

    /**
@@ -350,19 +356,32 @@ public final class StatsEvent {
            mPos = 0;
            writeTypeId(TYPE_OBJECT);

            // Set mPos to after atom id's location in the buffer.
            // First 2 elements in the buffer are event timestamp followed by the atom id.
            mPos = POS_ATOM_ID + Byte.BYTES + Integer.BYTES;
            mPosLastField = 0;
            mLastType = 0;
            // Write timestamp.
            mPos = POS_TIMESTAMP_NS;
            writeLong(mTimestampNs);
        }

        /**
         * Sets the atom id for this StatsEvent.
         *
         * This should be called immediately after StatsEvent.newBuilder()
         * and should only be called once.
         * Not calling setAtomId will result in ERROR_NO_ATOM_ID.
         * Calling setAtomId out of order will result in ERROR_ATOM_ID_INVALID_POSITION.
         **/
        @NonNull
        public Builder setAtomId(final int atomId) {
            if (0 == mAtomId) {
                mAtomId = atomId;

                if (1 == mNumElements) { // Only timestamp is written so far.
                    writeInt(atomId);
                } else {
                    // setAtomId called out of order.
                    mErrorMask |= ERROR_ATOM_ID_INVALID_POSITION;
                }
            }

            return this;
        }

@@ -557,7 +576,7 @@ public final class StatsEvent {
        public Builder addBooleanAnnotation(
                final byte annotationId, final boolean value) {
            // Ensure there's a field written to annotate.
            if (0 == mPosLastField) {
            if (mNumElements < 2) {
                mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
            } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
                mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
@@ -568,6 +587,7 @@ public final class StatsEvent {
                mCurrentAnnotationCount++;
                writeAnnotationCount();
            }

            return this;
        }

@@ -576,7 +596,7 @@ public final class StatsEvent {
         **/
        @NonNull
        public Builder addIntAnnotation(final byte annotationId, final int value) {
            if (0 == mPosLastField) {
            if (mNumElements < 2) {
                mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
            } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
                mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
@@ -587,6 +607,7 @@ public final class StatsEvent {
                mCurrentAnnotationCount++;
                writeAnnotationCount();
            }

            return this;
        }

@@ -619,19 +640,20 @@ public final class StatsEvent {
                mErrorMask |= ERROR_TOO_MANY_FIELDS;
            }

            int size = mPos;
            mPos = POS_TIMESTAMP_NS;
            writeLong(mTimestampNs);
            writeInt(mAtomId);
            if (0 == mErrorMask) {
                mBuffer.putByte(POS_NUM_ELEMENTS, (byte) mNumElements);
            } else {
                // Write atom id and error mask. Overwrite any annotations for atom Id.
                mPos = POS_ATOM_ID;
                mPos += mBuffer.putByte(mPos, TYPE_INT);
                mPos += mBuffer.putInt(mPos, mAtomId);
                mPos += mBuffer.putByte(mPos, TYPE_ERRORS);
                mPos += mBuffer.putInt(mPos, mErrorMask);
                mBuffer.putByte(POS_NUM_ELEMENTS, (byte) 3);
                size = mPos;
            }

            final int size = mPos;

            if (mUsePooledBuffer) {
                return new StatsEvent(mAtomId, mBuffer, mBuffer.getBytes(), size);
            } else {
+184 −0
Original line number Diff line number Diff line
@@ -84,6 +84,45 @@ public class StatsEventTest {
        statsEvent.release();
    }

    @Test
    public void testOnlyAtomId() {
        final int expectedAtomId = 109;

        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
        final StatsEvent statsEvent = StatsEvent.newBuilder()
                .setAtomId(expectedAtomId)
                .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(2);

        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);

        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());

        statsEvent.release();
    }

    @Test
    public void testIntBooleanIntInt() {
        final int expectedAtomId = 109;
@@ -460,6 +499,151 @@ public class StatsEventTest {
        statsEvent.release();
    }

    @Test
    public void testAtomIdAnnotations() {
        final int expectedAtomId = 109;
        final byte atomAnnotationId = 84;
        final int atomAnnotationValue = 9;
        final int field1 = 1;
        final byte field1AnnotationId = 45;
        final boolean field1AnnotationValue = false;
        final boolean field2 = true;
        final byte field2AnnotationId = 1;
        final int field2AnnotationValue = 23;

        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
        final StatsEvent statsEvent = StatsEvent.newBuilder()
                .setAtomId(expectedAtomId)
                .addIntAnnotation(atomAnnotationId, atomAnnotationValue)
                .writeInt(field1)
                .addBooleanAnnotation(field1AnnotationId, field1AnnotationValue)
                .writeBoolean(field2)
                .addIntAnnotation(field2AnnotationId, field2AnnotationValue)
                .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(4);

        assertWithMessage("First element is not timestamp")
                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);

        assertWithMessage("Incorrect timestamp")
                .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));

        final byte atomIdHeader = buffer.get();
        final int atomIdAnnotationValueCount = atomIdHeader >> 4;
        final byte atomIdValueType = (byte) (atomIdHeader & 0x0F);
        assertWithMessage("Second element is not atom id")
                .that(atomIdValueType).isEqualTo(StatsEvent.TYPE_INT);
        assertWithMessage("Atom id annotation count is wrong")
                .that(atomIdAnnotationValueCount).isEqualTo(1);
        assertWithMessage("Incorrect atom id")
                .that(buffer.getInt()).isEqualTo(expectedAtomId);
        assertWithMessage("Atom id's annotation id is wrong")
                .that(buffer.get()).isEqualTo(atomAnnotationId);
        assertWithMessage("Atom id's annotation type is wrong")
                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
        assertWithMessage("Atom id's annotation value is wrong")
                .that(buffer.getInt()).isEqualTo(atomAnnotationValue);

        final byte field1Header = buffer.get();
        final int field1AnnotationValueCount = field1Header >> 4;
        final byte field1Type = (byte) (field1Header & 0x0F);
        assertWithMessage("First field is not Int")
                .that(field1Type).isEqualTo(StatsEvent.TYPE_INT);
        assertWithMessage("First field annotation count is wrong")
                .that(field1AnnotationValueCount).isEqualTo(1);
        assertWithMessage("Incorrect field 1")
                .that(buffer.getInt()).isEqualTo(field1);
        assertWithMessage("First field's annotation id is wrong")
                .that(buffer.get()).isEqualTo(field1AnnotationId);
        assertWithMessage("First field's annotation type is wrong")
                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN);
        assertWithMessage("First field's annotation value is wrong")
                .that(buffer.get()).isEqualTo(field1AnnotationValue ? 1 : 0);

        final byte field2Header = buffer.get();
        final int field2AnnotationValueCount = field2Header >> 4;
        final byte field2Type = (byte) (field2Header & 0x0F);
        assertWithMessage("Second field is not boolean")
                .that(field2Type).isEqualTo(StatsEvent.TYPE_BOOLEAN);
        assertWithMessage("Second field annotation count is wrong")
                .that(field2AnnotationValueCount).isEqualTo(1);
        assertWithMessage("Incorrect field 2")
                .that(buffer.get()).isEqualTo(field2 ? 1 : 0);
        assertWithMessage("Second field's annotation id is wrong")
                .that(buffer.get()).isEqualTo(field2AnnotationId);
        assertWithMessage("Second field's annotation type is wrong")
                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
        assertWithMessage("Second field's annotation value is wrong")
                .that(buffer.getInt()).isEqualTo(field2AnnotationValue);

        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());

        statsEvent.release();
    }

    @Test
    public void testSetAtomIdNotCalledImmediately() {
        final int expectedAtomId = 109;
        final int field1 = 25;
        final boolean field2 = true;

        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
        final StatsEvent statsEvent = StatsEvent.newBuilder()
                .writeInt(field1)
                .setAtomId(expectedAtomId)
                .writeBoolean(field2)
                .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_ATOM_ID_INVALID_POSITION should be the only error in the mask")
                .that(errorMask).isEqualTo(StatsEvent.ERROR_ATOM_ID_INVALID_POSITION);

        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];