Merge changes Icaa2d90b,Ief489088
am: ec3e77e1d0
Change-Id: I7d30a9e23ed49ef525f3c2d449bcb74bee0bec8f
diff --git a/core/java/android/util/StatsEvent.java b/core/java/android/util/StatsEvent.java
index 91a5ec0..a21f9e0 100644
--- a/core/java/android/util/StatsEvent.java
+++ b/core/java/android/util/StatsEvent.java
@@ -20,312 +20,615 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
/**
* StatsEvent builds and stores the buffer sent over the statsd socket.
* This class defines and encapsulates the socket protocol.
+ *
+ * <p>Usage:</p>
+ * <pre>
+ * StatsEvent statsEvent = StatsEvent.newBuilder()
+ * .setAtomId(atomId)
+ * .writeBoolean(false)
+ * .writeString("annotated String field")
+ * .addBooleanAnnotation(annotationId, true)
+ * .build();
+ *
+ * StatsLog.write(statsEvent);
+ * </pre>
* @hide
**/
-public final class StatsEvent implements AutoCloseable {
- private static final int POS_NUM_ELEMENTS = 1;
- private static final int POS_TIMESTAMP = POS_NUM_ELEMENTS + 1;
-
+public final class StatsEvent {
private static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068;
- // Max payload size is 4 KB less 4 bytes which are reserved for statsEventTag.
+ // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag.
// See android_util_StatsLog.cpp.
- private static final int MAX_EVENT_PAYLOAD = LOGGER_ENTRY_MAX_PAYLOAD - 4;
+ private static final int MAX_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
- private static final byte INT_TYPE = 0;
- private static final byte LONG_TYPE = 1;
- private static final byte STRING_TYPE = 2;
- private static final byte LIST_TYPE = 3;
- private static final byte FLOAT_TYPE = 4;
+ private final int mAtomId;
+ private final Buffer mBuffer;
+ private final int mNumBytes;
- private static final int INT_TYPE_SIZE = 5;
- private static final int FLOAT_TYPE_SIZE = 5;
- private static final int LONG_TYPE_SIZE = 9;
-
- private static final int STRING_TYPE_OVERHEAD = 5;
- private static final int LIST_TYPE_OVERHEAD = 2;
-
- public static final int SUCCESS = 0;
- public static final int ERROR_BUFFER_LIMIT_EXCEEDED = -1;
- public static final int ERROR_NO_TIMESTAMP = -2;
- public static final int ERROR_TIMESTAMP_ALREADY_WRITTEN = -3;
- public static final int ERROR_NO_ATOM_ID = -4;
- public static final int ERROR_ATOM_ID_ALREADY_WRITTEN = -5;
- public static final int ERROR_UID_TAG_COUNT_MISMATCH = -6;
-
- private static Object sLock = new Object();
-
- @GuardedBy("sLock")
- private static StatsEvent sPool;
-
- private final byte[] mBuffer = new byte[MAX_EVENT_PAYLOAD];
- private int mPos;
- private int mNumElements;
- private int mAtomId;
-
- private StatsEvent() {
- // Write LIST_TYPE to buffer
- mBuffer[0] = LIST_TYPE;
- reset();
- }
-
- private void reset() {
- // Reset state.
- mPos = POS_TIMESTAMP;
- mNumElements = 0;
- mAtomId = 0;
+ private StatsEvent(final int atomId, @NonNull final Buffer buffer, final int numBytes) {
+ mAtomId = atomId;
+ mBuffer = buffer;
+ mNumBytes = numBytes;
}
/**
- * Returns a StatsEvent object from the pool.
+ * Returns a new StatsEvent.Builder for building StatsEvent object.
**/
@NonNull
- public static StatsEvent obtain() {
- final StatsEvent statsEvent;
- synchronized (sLock) {
- statsEvent = null == sPool ? new StatsEvent() : sPool;
- sPool = null;
- }
- statsEvent.reset();
- return statsEvent;
+ public StatsEvent.Builder newBuilder() {
+ return new StatsEvent.Builder(Buffer.obtain());
}
- @Override
- public void close() {
- synchronized (sLock) {
- if (null == sPool) {
- sPool = this;
- }
- }
- }
-
- /**
- * Writes the event timestamp to the buffer.
- **/
- public int writeTimestampNs(final long timestampNs) {
- if (hasTimestamp()) {
- return ERROR_TIMESTAMP_ALREADY_WRITTEN;
- }
- return writeLong(timestampNs);
- }
-
- private boolean hasTimestamp() {
- return mPos > POS_TIMESTAMP;
- }
-
- private boolean hasAtomId() {
- return mAtomId != 0;
- }
-
- /**
- * Writes the atom id to the buffer.
- **/
- public int writeAtomId(final int atomId) {
- if (!hasTimestamp()) {
- return ERROR_NO_TIMESTAMP;
- } else if (hasAtomId()) {
- return ERROR_ATOM_ID_ALREADY_WRITTEN;
- }
-
- final int writeResult = writeInt(atomId);
- if (SUCCESS == writeResult) {
- mAtomId = atomId;
- }
- return writeResult;
- }
-
- /**
- * Appends the given int to the StatsEvent buffer.
- **/
- public int writeInt(final int value) {
- if (!hasTimestamp()) {
- return ERROR_NO_TIMESTAMP;
- } else if (!hasAtomId()) {
- return ERROR_NO_ATOM_ID;
- } else if (mPos + INT_TYPE_SIZE > MAX_EVENT_PAYLOAD) {
- return ERROR_BUFFER_LIMIT_EXCEEDED;
- }
-
- mBuffer[mPos] = INT_TYPE;
- copyInt(mBuffer, mPos + 1, value);
- mPos += INT_TYPE_SIZE;
- mNumElements++;
- return SUCCESS;
- }
-
- /**
- * Appends the given long to the StatsEvent buffer.
- **/
- public int writeLong(final long value) {
- if (!hasTimestamp()) {
- return ERROR_NO_TIMESTAMP;
- } else if (!hasAtomId()) {
- return ERROR_NO_ATOM_ID;
- } else if (mPos + LONG_TYPE_SIZE > MAX_EVENT_PAYLOAD) {
- return ERROR_BUFFER_LIMIT_EXCEEDED;
- }
-
- mBuffer[mPos] = LONG_TYPE;
- copyLong(mBuffer, mPos + 1, value);
- mPos += LONG_TYPE_SIZE;
- mNumElements++;
- return SUCCESS;
- }
-
- /**
- * Appends the given float to the StatsEvent buffer.
- **/
- public int writeFloat(final float value) {
- if (!hasTimestamp()) {
- return ERROR_NO_TIMESTAMP;
- } else if (!hasAtomId()) {
- return ERROR_NO_ATOM_ID;
- } else if (mPos + FLOAT_TYPE_SIZE > MAX_EVENT_PAYLOAD) {
- return ERROR_BUFFER_LIMIT_EXCEEDED;
- }
-
- mBuffer[mPos] = FLOAT_TYPE;
- copyInt(mBuffer, mPos + 1, Float.floatToIntBits(value));
- mPos += FLOAT_TYPE_SIZE;
- mNumElements++;
- return SUCCESS;
- }
-
- /**
- * Appends the given boolean to the StatsEvent buffer.
- **/
- public int writeBoolean(final boolean value) {
- return writeInt(value ? 1 : 0);
- }
-
- /**
- * Appends the given byte array to the StatsEvent buffer.
- **/
- public int writeByteArray(@NonNull final byte[] value) {
- if (!hasTimestamp()) {
- return ERROR_NO_TIMESTAMP;
- } else if (!hasAtomId()) {
- return ERROR_NO_ATOM_ID;
- } else if (mPos + STRING_TYPE_OVERHEAD + value.length > MAX_EVENT_PAYLOAD) {
- return ERROR_BUFFER_LIMIT_EXCEEDED;
- }
-
- mBuffer[mPos] = STRING_TYPE;
- copyInt(mBuffer, mPos + 1, value.length);
- System.arraycopy(value, 0, mBuffer, mPos + STRING_TYPE_OVERHEAD, value.length);
- mPos += STRING_TYPE_OVERHEAD + value.length;
- mNumElements++;
- return SUCCESS;
- }
-
- /**
- * Appends the given String to the StatsEvent buffer.
- **/
- public int writeString(@NonNull final String value) {
- final byte[] valueBytes = stringToBytes(value);
- return writeByteArray(valueBytes);
- }
-
- /**
- * Appends the AttributionNode specified as array of uids and array of tags.
- **/
- public int writeAttributionNode(@NonNull final int[] uids, @NonNull final String[] tags) {
- if (!hasTimestamp()) {
- return ERROR_NO_TIMESTAMP;
- } else if (!hasAtomId()) {
- return ERROR_NO_ATOM_ID;
- } else if (mPos + LIST_TYPE_OVERHEAD > MAX_EVENT_PAYLOAD) {
- return ERROR_BUFFER_LIMIT_EXCEEDED;
- }
-
- final int numTags = tags.length;
- final int numUids = uids.length;
- if (numTags != numUids) {
- return ERROR_UID_TAG_COUNT_MISMATCH;
- }
-
- int pos = mPos;
- mBuffer[pos] = LIST_TYPE;
- mBuffer[pos + 1] = (byte) numTags;
- pos += LIST_TYPE_OVERHEAD;
- for (int i = 0; i < numTags; i++) {
- final byte[] tagBytes = stringToBytes(tags[i]);
-
- if (pos + LIST_TYPE_OVERHEAD + INT_TYPE_SIZE
- + STRING_TYPE_OVERHEAD + tagBytes.length > MAX_EVENT_PAYLOAD) {
- return ERROR_BUFFER_LIMIT_EXCEEDED;
- }
-
- mBuffer[pos] = LIST_TYPE;
- mBuffer[pos + 1] = 2;
- pos += LIST_TYPE_OVERHEAD;
- mBuffer[pos] = INT_TYPE;
- copyInt(mBuffer, pos + 1, uids[i]);
- pos += INT_TYPE_SIZE;
- mBuffer[pos] = STRING_TYPE;
- copyInt(mBuffer, pos + 1, tagBytes.length);
- System.arraycopy(tagBytes, 0, mBuffer, pos + STRING_TYPE_OVERHEAD, tagBytes.length);
- pos += STRING_TYPE_OVERHEAD + tagBytes.length;
- }
- mPos = pos;
- mNumElements++;
- return SUCCESS;
- }
-
- /**
- * Returns the byte array containing data in the statsd socket format.
- * @hide
- **/
- @NonNull
- public byte[] getBuffer() {
- // Encode number of elements in the buffer.
- mBuffer[POS_NUM_ELEMENTS] = (byte) mNumElements;
- return mBuffer;
- }
-
- /**
- * Returns number of bytes used by the buffer.
- * @hide
- **/
- public int size() {
- return mPos;
- }
-
- /**
- * Getter for atom id.
- * @hide
- **/
- public int getAtomId() {
+ int getAtomId() {
return mAtomId;
}
@NonNull
- private static byte[] stringToBytes(@Nullable final String value) {
- return (null == value ? "" : value).getBytes(UTF_8);
+ byte[] getBytes() {
+ return mBuffer.getBytes();
}
- // Helper methods for copying primitives
- private static void copyInt(@NonNull byte[] buff, int pos, int value) {
- buff[pos] = (byte) (value);
- buff[pos + 1] = (byte) (value >> 8);
- buff[pos + 2] = (byte) (value >> 16);
- buff[pos + 3] = (byte) (value >> 24);
+ int getNumBytes() {
+ return mNumBytes;
}
- private static void copyLong(@NonNull byte[] buff, int pos, long value) {
- buff[pos] = (byte) (value);
- buff[pos + 1] = (byte) (value >> 8);
- buff[pos + 2] = (byte) (value >> 16);
- buff[pos + 3] = (byte) (value >> 24);
- buff[pos + 4] = (byte) (value >> 32);
- buff[pos + 5] = (byte) (value >> 40);
- buff[pos + 6] = (byte) (value >> 48);
- buff[pos + 7] = (byte) (value >> 56);
+ void release() {
+ mBuffer.release();
+ }
+
+ /**
+ * Builder for constructing a StatsEvent object.
+ *
+ * <p>This class defines and encapsulates the socket encoding for the buffer.
+ * The write methods must be called in the same order as the order of fields in the
+ * atom definition.</p>
+ *
+ * <p>setAtomId() can be called anytime before build().</p>
+ *
+ * <p>Example:</p>
+ * <pre>
+ * // Atom definition.
+ * message MyAtom {
+ * optional int32 field1 = 1;
+ * optional int64 field2 = 2;
+ * optional string field3 = 3 [(annotation1) = true];
+ * }
+ *
+ * // StatsEvent construction.
+ * StatsEvent.newBuilder()
+ * StatsEvent statsEvent = StatsEvent.newBuilder()
+ * .setAtomId(atomId)
+ * .writeInt(3) // field1
+ * .writeLong(8L) // field2
+ * .writeString("foo") // field 3
+ * .addBooleanAnnotation(annotation1Id, true)
+ * .build();
+ * </pre>
+ * @hide
+ **/
+ public static final class Builder {
+ // Type Ids.
+ private static final byte TYPE_INT = 0x00;
+ private static final byte TYPE_LONG = 0x01;
+ private static final byte TYPE_STRING = 0x02;
+ private static final byte TYPE_LIST = 0x03;
+ private static final byte TYPE_FLOAT = 0x04;
+ private static final byte TYPE_BOOLEAN = 0x05;
+ private static final byte TYPE_BYTE_ARRAY = 0x06;
+ private static final byte TYPE_OBJECT = 0x07;
+ private static final byte TYPE_KEY_VALUE_PAIRS = 0x08;
+ private static final byte TYPE_ATTRIBUTION_CHAIN = 0x09;
+ private static final byte TYPE_ERRORS = 0x0F;
+
+ // Error flags.
+ private static final int ERROR_NO_TIMESTAMP = 0x1;
+ private static final int ERROR_NO_ATOM_ID = 0x2;
+ private static final int ERROR_OVERFLOW = 0x4;
+ private static final int ERROR_ATTRIBUTION_CHAIN_TOO_LONG = 0x8;
+ private static final int ERROR_TOO_MANY_KEY_VALUE_PAIRS = 0x10;
+ private static final int ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD = 0x20;
+ private static final int ERROR_INVALID_ANNOTATION_ID = 0x40;
+ private static final int ERROR_ANNOTATION_ID_TOO_LARGE = 0x80;
+ private static final int ERROR_TOO_MANY_ANNOTATIONS = 0x100;
+ private static final int ERROR_TOO_MANY_FIELDS = 0x200;
+ private static final int ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL = 0x400;
+
+ // Size limits.
+ private static final int MAX_ANNOTATION_COUNT = 15;
+ private static final int MAX_ATTRIBUTION_NODES = 127;
+ private static final int MAX_NUM_ELEMENTS = 127;
+ private static final int MAX_KEY_VALUE_PAIRS = 127;
+
+ // Fixed positions.
+ private static final int POS_NUM_ELEMENTS = 1;
+ private static final int POS_TIMESTAMP_NS = POS_NUM_ELEMENTS + Byte.BYTES;
+ private static final int POS_ATOM_ID = POS_TIMESTAMP_NS + Byte.BYTES + Long.BYTES;
+
+ private final Buffer mBuffer;
+ private long mTimestampNs;
+ private int mAtomId;
+ private byte mCurrentAnnotationCount;
+ private int mPos;
+ private int mPosLastField;
+ private byte mLastType;
+ private int mNumElements;
+ private int mErrorMask;
+
+ private Builder(final Buffer buffer) {
+ mBuffer = buffer;
+ mCurrentAnnotationCount = 0;
+ mAtomId = 0;
+ mTimestampNs = SystemClock.elapsedRealtimeNanos();
+ mNumElements = 0;
+
+ // Set mPos to 0 for writing TYPE_OBJECT at 0th position.
+ 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;
+ }
+
+ /**
+ * Sets the atom id for this StatsEvent.
+ **/
+ @NonNull
+ public Builder setAtomId(final int atomId) {
+ mAtomId = atomId;
+ return this;
+ }
+
+ /**
+ * Sets the timestamp in nanos for this StatsEvent.
+ **/
+ @VisibleForTesting
+ @NonNull
+ public Builder setTimestampNs(final long timestampNs) {
+ mTimestampNs = timestampNs;
+ return this;
+ }
+
+ /**
+ * Write a boolean field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeBoolean(final boolean value) {
+ // Write boolean typeId byte followed by boolean byte representation.
+ writeTypeId(TYPE_BOOLEAN);
+ mPos += mBuffer.putBoolean(mPos, value);
+ mNumElements++;
+ return this;
+ }
+
+ /**
+ * Write an integer field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeInt(final int value) {
+ // Write integer typeId byte followed by 4-byte representation of value.
+ writeTypeId(TYPE_INT);
+ mPos += mBuffer.putInt(mPos, value);
+ mNumElements++;
+ return this;
+ }
+
+ /**
+ * Write a long field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeLong(final long value) {
+ // Write long typeId byte followed by 8-byte representation of value.
+ writeTypeId(TYPE_LONG);
+ mPos += mBuffer.putLong(mPos, value);
+ mNumElements++;
+ return this;
+ }
+
+ /**
+ * Write a float field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeFloat(final float value) {
+ // Write float typeId byte followed by 4-byte representation of value.
+ writeTypeId(TYPE_FLOAT);
+ mPos += mBuffer.putFloat(mPos, value);
+ mNumElements++;
+ return this;
+ }
+
+ /**
+ * Write a String field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeString(@NonNull final String value) {
+ // Write String typeId byte, followed by 4-byte representation of number of bytes
+ // in the UTF-8 encoding, followed by the actual UTF-8 byte encoding of value.
+ final byte[] valueBytes = stringToBytes(value);
+ writeByteArray(valueBytes, TYPE_STRING);
+ return this;
+ }
+
+ /**
+ * Write a byte array field to this StatsEvent.
+ **/
+ @NonNull
+ public Builder writeByteArray(@NonNull final byte[] value) {
+ // Write byte array typeId byte, followed by 4-byte representation of number of bytes
+ // in value, followed by the actual byte array.
+ writeByteArray(value, TYPE_BYTE_ARRAY);
+ return this;
+ }
+
+ private void writeByteArray(@NonNull final byte[] value, final byte typeId) {
+ writeTypeId(typeId);
+ final int numBytes = value.length;
+ mPos += mBuffer.putInt(mPos, numBytes);
+ mPos += mBuffer.putByteArray(mPos, value);
+ mNumElements++;
+ }
+
+ /**
+ * Write an attribution chain field to this StatsEvent.
+ *
+ * The sizes of uids and tags must be equal. The AttributionNode at position i is
+ * made up of uids[i] and tags[i].
+ *
+ * @param uids array of uids in the attribution nodes.
+ * @param tags array of tags in the attribution nodes.
+ **/
+ @NonNull
+ public Builder writeAttributionNode(
+ @NonNull final int[] uids, @NonNull final String[] tags) {
+ final byte numUids = (byte) uids.length;
+ final byte numTags = (byte) tags.length;
+
+ if (numUids != numTags) {
+ mErrorMask |= ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL;
+ } else if (numUids > MAX_ATTRIBUTION_NODES) {
+ mErrorMask |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG;
+ } else {
+ // Write attribution chain typeId byte, followed by 1-byte representation of
+ // number of attribution nodes, followed by encoding of each attribution node.
+ writeTypeId(TYPE_ATTRIBUTION_CHAIN);
+ mPos += mBuffer.putByte(mPos, numUids);
+ for (int i = 0; i < numUids; i++) {
+ // Each uid is encoded as 4-byte representation of its int value.
+ mPos += mBuffer.putInt(mPos, uids[i]);
+
+ // Each tag is encoded as 4-byte representation of number of bytes in its
+ // UTF-8 encoding, followed by the actual UTF-8 bytes.
+ final byte[] tagBytes = stringToBytes(tags[i]);
+ mPos += mBuffer.putInt(mPos, tagBytes.length);
+ mPos += mBuffer.putByteArray(mPos, tagBytes);
+ }
+ mNumElements++;
+ }
+ return this;
+ }
+
+ /**
+ * Write KeyValuePairsAtom entries to this StatsEvent.
+ *
+ * @param intMap Integer key-value pairs.
+ * @param longMap Long key-value pairs.
+ * @param stringMap String key-value pairs.
+ * @param floatMap Float key-value pairs.
+ **/
+ @NonNull
+ public Builder writeKeyValuePairs(
+ @NonNull final SparseIntArray intMap,
+ @NonNull final SparseLongArray longMap,
+ @NonNull final SparseArray<String> stringMap,
+ @NonNull final SparseArray<Float> floatMap) {
+ final int intMapSize = intMap.size();
+ final int longMapSize = longMap.size();
+ final int stringMapSize = stringMap.size();
+ final int floatMapSize = floatMap.size();
+ final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize;
+
+ if (totalCount > MAX_KEY_VALUE_PAIRS) {
+ mErrorMask |= ERROR_TOO_MANY_KEY_VALUE_PAIRS;
+ } else {
+ writeTypeId(TYPE_KEY_VALUE_PAIRS);
+ mPos += mBuffer.putByte(mPos, (byte) totalCount);
+
+ for (int i = 0; i < intMapSize; i++) {
+ final int key = intMap.keyAt(i);
+ final int value = intMap.valueAt(i);
+ mPos += mBuffer.putInt(mPos, key);
+ writeTypeId(TYPE_INT);
+ mPos += mBuffer.putInt(mPos, value);
+ }
+
+ for (int i = 0; i < longMapSize; i++) {
+ final int key = longMap.keyAt(i);
+ final long value = longMap.valueAt(i);
+ mPos += mBuffer.putInt(mPos, key);
+ writeTypeId(TYPE_LONG);
+ mPos += mBuffer.putLong(mPos, value);
+ }
+
+ for (int i = 0; i < stringMapSize; i++) {
+ final int key = stringMap.keyAt(i);
+ final String value = stringMap.valueAt(i);
+ mPos += mBuffer.putInt(mPos, key);
+ writeTypeId(TYPE_STRING);
+ final byte[] valueBytes = stringToBytes(value);
+ mPos += mBuffer.putInt(mPos, valueBytes.length);
+ mPos += mBuffer.putByteArray(mPos, valueBytes);
+ }
+
+ for (int i = 0; i < floatMapSize; i++) {
+ final int key = floatMap.keyAt(i);
+ final float value = floatMap.valueAt(i);
+ mPos += mBuffer.putInt(mPos, key);
+ writeTypeId(TYPE_FLOAT);
+ mPos += mBuffer.putFloat(mPos, value);
+ }
+
+ mNumElements++;
+ }
+
+ return this;
+ }
+
+ /**
+ * Write a boolean annotation for the last field written.
+ **/
+ @NonNull
+ public Builder addBooleanAnnotation(
+ final byte annotationId, final boolean value) {
+ // Ensure there's a field written to annotate.
+ if (0 == mPosLastField) {
+ mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+ } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
+ mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
+ } else {
+ mPos += mBuffer.putByte(mPos, annotationId);
+ mPos += mBuffer.putByte(mPos, TYPE_BOOLEAN);
+ mPos += mBuffer.putBoolean(mPos, value);
+ mCurrentAnnotationCount++;
+ writeAnnotationCount();
+ }
+ return this;
+ }
+
+ /**
+ * Write an integer annotation for the last field written.
+ **/
+ @NonNull
+ public Builder addIntAnnotation(final byte annotationId, final int value) {
+ if (0 == mPosLastField) {
+ mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+ } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
+ mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
+ } else {
+ mPos += mBuffer.putByte(mPos, annotationId);
+ mPos += mBuffer.putByte(mPos, TYPE_INT);
+ mPos += mBuffer.putInt(mPos, value);
+ mCurrentAnnotationCount++;
+ writeAnnotationCount();
+ }
+ return this;
+ }
+
+ /**
+ * Builds a StatsEvent object with values entered in this Builder.
+ **/
+ @NonNull
+ public StatsEvent build() {
+ if (0L == mTimestampNs) {
+ mErrorMask |= ERROR_NO_TIMESTAMP;
+ }
+ if (0 == mAtomId) {
+ mErrorMask |= ERROR_NO_ATOM_ID;
+ }
+ if (mBuffer.hasOverflowed()) {
+ mErrorMask |= ERROR_OVERFLOW;
+ }
+ if (mNumElements > MAX_NUM_ELEMENTS) {
+ 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 {
+ mBuffer.putByte(0, TYPE_ERRORS);
+ mBuffer.putByte(POS_NUM_ELEMENTS, (byte) 3);
+ mPos += mBuffer.putInt(mPos, mErrorMask);
+ size = mPos;
+ }
+
+ return new StatsEvent(mAtomId, mBuffer, size);
+ }
+
+ private void writeTypeId(final byte typeId) {
+ mPosLastField = mPos;
+ mLastType = typeId;
+ mCurrentAnnotationCount = 0;
+ final byte encodedId = (byte) (typeId & 0x0F);
+ mPos += mBuffer.putByte(mPos, encodedId);
+ }
+
+ private void writeAnnotationCount() {
+ // Use first 4 bits for annotation count and last 4 bits for typeId.
+ final byte encodedId = (byte) ((mCurrentAnnotationCount << 4) | (mLastType & 0x0F));
+ mBuffer.putByte(mPosLastField, encodedId);
+ }
+
+ @NonNull
+ private static byte[] stringToBytes(@Nullable final String value) {
+ return (null == value ? "" : value).getBytes(UTF_8);
+ }
+ }
+
+ private static final class Buffer {
+ private static Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static Buffer sPool;
+
+ private final byte[] mBytes = new byte[MAX_PAYLOAD_SIZE];
+ private boolean mOverflow = false;
+
+ @NonNull
+ private static Buffer obtain() {
+ final Buffer buffer;
+ synchronized (sLock) {
+ buffer = null == sPool ? new Buffer() : sPool;
+ sPool = null;
+ }
+ buffer.reset();
+ return buffer;
+ }
+
+ private Buffer() {
+ }
+
+ @NonNull
+ private byte[] getBytes() {
+ return mBytes;
+ }
+
+ private void release() {
+ synchronized (sLock) {
+ if (null == sPool) {
+ sPool = this;
+ }
+ }
+ }
+
+ private void reset() {
+ mOverflow = false;
+ }
+
+ private boolean hasOverflowed() {
+ return mOverflow;
+ }
+
+ /**
+ * Checks for available space in the byte array.
+ *
+ * @param index starting position in the buffer to start the check.
+ * @param numBytes number of bytes to check from index.
+ * @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) {
+ mOverflow = true;
+ }
+ return result;
+ }
+
+ /**
+ * Writes a byte into the buffer.
+ *
+ * @param index position in the buffer where the byte is written.
+ * @param value the byte to write.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putByte(final int index, final byte value) {
+ if (hasEnoughSpace(index, Byte.BYTES)) {
+ mBytes[index] = (byte) (value);
+ return Byte.BYTES;
+ }
+ return 0;
+ }
+
+ /**
+ * Writes a boolean into the buffer.
+ *
+ * @param index position in the buffer where the boolean is written.
+ * @param value the boolean to write.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putBoolean(final int index, final boolean value) {
+ return putByte(index, (byte) (value ? 1 : 0));
+ }
+
+ /**
+ * Writes an integer into the buffer.
+ *
+ * @param index position in the buffer where the integer is written.
+ * @param value the integer to write.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putInt(final int index, final int value) {
+ if (hasEnoughSpace(index, Integer.BYTES)) {
+ // Use little endian byte order.
+ mBytes[index] = (byte) (value);
+ mBytes[index + 1] = (byte) (value >> 8);
+ mBytes[index + 2] = (byte) (value >> 16);
+ mBytes[index + 3] = (byte) (value >> 24);
+ return Integer.BYTES;
+ }
+ return 0;
+ }
+
+ /**
+ * Writes a long into the buffer.
+ *
+ * @param index position in the buffer where the long is written.
+ * @param value the long to write.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putLong(final int index, final long value) {
+ if (hasEnoughSpace(index, Long.BYTES)) {
+ // Use little endian byte order.
+ mBytes[index] = (byte) (value);
+ mBytes[index + 1] = (byte) (value >> 8);
+ mBytes[index + 2] = (byte) (value >> 16);
+ mBytes[index + 3] = (byte) (value >> 24);
+ mBytes[index + 4] = (byte) (value >> 32);
+ mBytes[index + 5] = (byte) (value >> 40);
+ mBytes[index + 6] = (byte) (value >> 48);
+ mBytes[index + 7] = (byte) (value >> 56);
+ return Long.BYTES;
+ }
+ return 0;
+ }
+
+ /**
+ * Writes a float into the buffer.
+ *
+ * @param index position in the buffer where the float is written.
+ * @param value the float to write.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putFloat(final int index, final float value) {
+ return putInt(index, Float.floatToIntBits(value));
+ }
+
+ /**
+ * Copies a byte array into the buffer.
+ *
+ * @param index position in the buffer where the byte array is copied.
+ * @param value the byte array to copy.
+ * @return number of bytes written to buffer from this write operation.
+ **/
+ private int putByteArray(final int index, @NonNull final byte[] value) {
+ final int numBytes = value.length;
+ if (hasEnoughSpace(index, numBytes)) {
+ System.arraycopy(value, 0, mBytes, index, numBytes);
+ return numBytes;
+ }
+ return 0;
+ }
}
}