1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util;
18 
19 import static java.nio.charset.StandardCharsets.UTF_8;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SystemApi;
24 import android.os.Build;
25 import android.os.SystemClock;
26 
27 import androidx.annotation.RequiresApi;
28 
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.nio.ByteBuffer;
33 import java.util.Arrays;
34 
35 /**
36  * StatsEvent builds and stores the buffer sent over the statsd socket.
37  * This class defines and encapsulates the socket protocol.
38  *
39  * <p>Usage:</p>
40  * <pre>
41  *      // Pushed event
42  *      StatsEvent statsEvent = StatsEvent.newBuilder()
43  *          .setAtomId(atomId)
44  *          .writeBoolean(false)
45  *          .writeString("annotated String field")
46  *          .addBooleanAnnotation(annotationId, true)
47  *          .usePooledBuffer()
48  *          .build();
49  *      StatsLog.write(statsEvent);
50  *
51  *      // Pulled event
52  *      StatsEvent statsEvent = StatsEvent.newBuilder()
53  *          .setAtomId(atomId)
54  *          .writeBoolean(false)
55  *          .writeString("annotated String field")
56  *          .addBooleanAnnotation(annotationId, true)
57  *          .build();
58  * </pre>
59  * @hide
60  **/
61 @SystemApi
62 public final class StatsEvent {
63     // Type Ids.
64     /**
65      * @hide
66      **/
67     @VisibleForTesting
68     public static final byte TYPE_INT = 0x00;
69 
70     /**
71      * @hide
72      **/
73     @VisibleForTesting
74     public static final byte TYPE_LONG = 0x01;
75 
76     /**
77      * @hide
78      **/
79     @VisibleForTesting
80     public static final byte TYPE_STRING = 0x02;
81 
82     /**
83      * @hide
84      **/
85     @VisibleForTesting
86     public static final byte TYPE_LIST = 0x03;
87 
88     /**
89      * @hide
90      **/
91     @VisibleForTesting
92     public static final byte TYPE_FLOAT = 0x04;
93 
94     /**
95      * @hide
96      **/
97     @VisibleForTesting
98     public static final byte TYPE_BOOLEAN = 0x05;
99 
100     /**
101      * @hide
102      **/
103     @VisibleForTesting
104     public static final byte TYPE_BYTE_ARRAY = 0x06;
105 
106     /**
107      * @hide
108      **/
109     @VisibleForTesting
110     public static final byte TYPE_OBJECT = 0x07;
111 
112     /**
113      * @hide
114      **/
115     @VisibleForTesting
116     public static final byte TYPE_KEY_VALUE_PAIRS = 0x08;
117 
118     /**
119      * @hide
120      **/
121     @VisibleForTesting
122     public static final byte TYPE_ATTRIBUTION_CHAIN = 0x09;
123 
124     /**
125      * @hide
126      **/
127     @VisibleForTesting
128     public static final byte TYPE_ERRORS = 0x0F;
129 
130     // Error flags.
131     /**
132      * @hide
133      **/
134     @VisibleForTesting
135     public static final int ERROR_NO_TIMESTAMP = 0x1;
136 
137     /**
138      * @hide
139      **/
140     @VisibleForTesting
141     public static final int ERROR_NO_ATOM_ID = 0x2;
142 
143     /**
144      * @hide
145      **/
146     @VisibleForTesting
147     public static final int ERROR_OVERFLOW = 0x4;
148 
149     /**
150      * @hide
151      **/
152     @VisibleForTesting
153     public static final int ERROR_ATTRIBUTION_CHAIN_TOO_LONG = 0x8;
154 
155     /**
156      * @hide
157      **/
158     @VisibleForTesting
159     public static final int ERROR_TOO_MANY_KEY_VALUE_PAIRS = 0x10;
160 
161     /**
162      * @hide
163      **/
164     @VisibleForTesting
165     public static final int ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD = 0x20;
166 
167     /**
168      * @hide
169      **/
170     @VisibleForTesting
171     public static final int ERROR_INVALID_ANNOTATION_ID = 0x40;
172 
173     /**
174      * @hide
175      **/
176     @VisibleForTesting
177     public static final int ERROR_ANNOTATION_ID_TOO_LARGE = 0x80;
178 
179     /**
180      * @hide
181      **/
182     @VisibleForTesting
183     public static final int ERROR_TOO_MANY_ANNOTATIONS = 0x100;
184 
185     /**
186      * @hide
187      **/
188     @VisibleForTesting
189     public static final int ERROR_TOO_MANY_FIELDS = 0x200;
190 
191     /**
192      * @hide
193      **/
194     @VisibleForTesting
195     public static final int ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL = 0x1000;
196 
197     /**
198      * @hide
199      **/
200     @VisibleForTesting
201     public static final int ERROR_ATOM_ID_INVALID_POSITION = 0x2000;
202 
203     /**
204      * @hide
205      **/
206     @VisibleForTesting public static final int ERROR_LIST_TOO_LONG = 0x4000;
207 
208     // Size limits.
209 
210     /**
211      * @hide
212      **/
213     @VisibleForTesting
214     public static final int MAX_ANNOTATION_COUNT = 15;
215 
216     /**
217      * @hide
218      **/
219     @VisibleForTesting
220     public static final int MAX_ATTRIBUTION_NODES = 127;
221 
222     /**
223      * @hide
224      **/
225     @VisibleForTesting
226     public static final int MAX_NUM_ELEMENTS = 127;
227 
228     /**
229      * @hide
230      **/
231     @VisibleForTesting
232     public static final int MAX_KEY_VALUE_PAIRS = 127;
233 
234     private static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068;
235 
236     // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag.
237     // See android_util_StatsLog.cpp.
238     private static final int MAX_PUSH_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
239 
240     private static final int MAX_PULL_PAYLOAD_SIZE = 50 * 1024; // 50 KB
241 
242     private final int mAtomId;
243     private final byte[] mPayload;
244     private Buffer mBuffer;
245     private final int mNumBytes;
246 
StatsEvent(final int atomId, @Nullable final Buffer buffer, @NonNull final byte[] payload, final int numBytes)247     private StatsEvent(final int atomId, @Nullable final Buffer buffer,
248             @NonNull final byte[] payload, final int numBytes) {
249         mAtomId = atomId;
250         mBuffer = buffer;
251         mPayload = payload;
252         mNumBytes = numBytes;
253     }
254 
255     /**
256      * Returns a new StatsEvent.Builder for building StatsEvent object.
257      **/
258     @NonNull
newBuilder()259     public static StatsEvent.Builder newBuilder() {
260         return new StatsEvent.Builder(Buffer.obtain());
261     }
262 
263     /**
264      * Get the atom Id of the atom encoded in this StatsEvent object.
265      *
266      * @hide
267      **/
getAtomId()268     public int getAtomId() {
269         return mAtomId;
270     }
271 
272     /**
273      * Get the byte array that contains the encoded payload that can be sent to statsd.
274      *
275      * @hide
276      **/
277     @NonNull
getBytes()278     public byte[] getBytes() {
279         return mPayload;
280     }
281 
282     /**
283      * Get the number of bytes used to encode the StatsEvent payload.
284      *
285      * @hide
286      **/
getNumBytes()287     public int getNumBytes() {
288         return mNumBytes;
289     }
290 
291     /**
292      * Recycle resources used by this StatsEvent object.
293      * No actions should be taken on this StatsEvent after release() is called.
294      *
295      * @hide
296      **/
release()297     public void release() {
298         if (mBuffer != null) {
299             mBuffer.release();
300             mBuffer = null;
301         }
302     }
303 
304     /**
305      * Builder for constructing a StatsEvent object.
306      *
307      * <p>This class defines and encapsulates the socket encoding for the
308      *buffer. The write methods must be called in the same order as the order of
309      *fields in the atom definition.</p>
310      *
311      * <p>setAtomId() must be called immediately after
312      *StatsEvent.newBuilder().</p>
313      *
314      * <p>Example:</p>
315      * <pre>
316      *     // Atom definition.
317      *     message MyAtom {
318      *         optional int32 field1 = 1;
319      *         optional int64 field2 = 2;
320      *         optional string field3 = 3 [(annotation1) = true];
321      *         optional repeated int32 field4 = 4;
322      *     }
323      *
324      *     // StatsEvent construction for pushed event.
325      *     StatsEvent.newBuilder()
326      *     StatsEvent statsEvent = StatsEvent.newBuilder()
327      *         .setAtomId(atomId)
328      *         .writeInt(3) // field1
329      *         .writeLong(8L) // field2
330      *         .writeString("foo") // field 3
331      *         .addBooleanAnnotation(annotation1Id, true)
332      *         .writeIntArray({ 1, 2, 3 });
333      *         .usePooledBuffer()
334      *         .build();
335      *
336      *     // StatsEvent construction for pulled event.
337      *     StatsEvent.newBuilder()
338      *     StatsEvent statsEvent = StatsEvent.newBuilder()
339      *         .setAtomId(atomId)
340      *         .writeInt(3) // field1
341      *         .writeLong(8L) // field2
342      *         .writeString("foo") // field 3
343      *         .addBooleanAnnotation(annotation1Id, true)
344      *         .writeIntArray({ 1, 2, 3 });
345      *         .build();
346      * </pre>
347      **/
348     public static final class Builder {
349         // Fixed positions.
350         private static final int POS_NUM_ELEMENTS = 1;
351         private static final int POS_TIMESTAMP_NS = POS_NUM_ELEMENTS + Byte.BYTES;
352         private static final int POS_ATOM_ID = POS_TIMESTAMP_NS + Byte.BYTES + Long.BYTES;
353 
354         private final Buffer mBuffer;
355         private long mTimestampNs;
356         private int mAtomId;
357         private byte mCurrentAnnotationCount;
358         private int mPos;
359         private int mPosLastField;
360         private byte mLastType;
361         private int mNumElements;
362         private int mErrorMask;
363         private boolean mUsePooledBuffer = false;
364 
Builder(final Buffer buffer)365         private Builder(final Buffer buffer) {
366             mBuffer = buffer;
367             mCurrentAnnotationCount = 0;
368             mAtomId = 0;
369             mTimestampNs = SystemClock.elapsedRealtimeNanos();
370             mNumElements = 0;
371 
372             // Set mPos to 0 for writing TYPE_OBJECT at 0th position.
373             mPos = 0;
374             writeTypeId(TYPE_OBJECT);
375 
376             // Write timestamp.
377             mPos = POS_TIMESTAMP_NS;
378             writeLong(mTimestampNs);
379         }
380 
381         /**
382          * Sets the atom id for this StatsEvent.
383          *
384          * This should be called immediately after StatsEvent.newBuilder()
385          * and should only be called once.
386          * Not calling setAtomId will result in ERROR_NO_ATOM_ID.
387          * Calling setAtomId out of order will result in ERROR_ATOM_ID_INVALID_POSITION.
388          **/
389         @NonNull
setAtomId(final int atomId)390         public Builder setAtomId(final int atomId) {
391             if (0 == mAtomId) {
392                 mAtomId = atomId;
393 
394                 if (1 == mNumElements) { // Only timestamp is written so far.
395                     writeInt(atomId);
396                 } else {
397                     // setAtomId called out of order.
398                     mErrorMask |= ERROR_ATOM_ID_INVALID_POSITION;
399                 }
400             }
401 
402             return this;
403         }
404 
405         /**
406          * Write a boolean field to this StatsEvent.
407          **/
408         @NonNull
writeBoolean(final boolean value)409         public Builder writeBoolean(final boolean value) {
410             // Write boolean typeId byte followed by boolean byte representation.
411             writeTypeId(TYPE_BOOLEAN);
412             mPos += mBuffer.putBoolean(mPos, value);
413             mNumElements++;
414             return this;
415         }
416 
417         /**
418          * Write an integer field to this StatsEvent.
419          **/
420         @NonNull
writeInt(final int value)421         public Builder writeInt(final int value) {
422             // Write integer typeId byte followed by 4-byte representation of value.
423             writeTypeId(TYPE_INT);
424             mPos += mBuffer.putInt(mPos, value);
425             mNumElements++;
426             return this;
427         }
428 
429         /**
430          * Write a long field to this StatsEvent.
431          **/
432         @NonNull
writeLong(final long value)433         public Builder writeLong(final long value) {
434             // Write long typeId byte followed by 8-byte representation of value.
435             writeTypeId(TYPE_LONG);
436             mPos += mBuffer.putLong(mPos, value);
437             mNumElements++;
438             return this;
439         }
440 
441         /**
442          * Write a float field to this StatsEvent.
443          **/
444         @NonNull
writeFloat(final float value)445         public Builder writeFloat(final float value) {
446             // Write float typeId byte followed by 4-byte representation of value.
447             writeTypeId(TYPE_FLOAT);
448             mPos += mBuffer.putFloat(mPos, value);
449             mNumElements++;
450             return this;
451         }
452 
453         /**
454          * Write a String field to this StatsEvent.
455          **/
456         @NonNull
writeString(@onNull final String value)457         public Builder writeString(@NonNull final String value) {
458             // Write String typeId byte, followed by 4-byte representation of number of bytes
459             // in the UTF-8 encoding, followed by the actual UTF-8 byte encoding of value.
460             final byte[] valueBytes = stringToBytes(value);
461             writeByteArray(valueBytes, TYPE_STRING);
462             return this;
463         }
464 
465         /**
466          * Write a byte array field to this StatsEvent.
467          **/
468         @NonNull
writeByteArray(@onNull final byte[] value)469         public Builder writeByteArray(@NonNull final byte[] value) {
470             // Write byte array typeId byte, followed by 4-byte representation of number of bytes
471             // in value, followed by the actual byte array.
472             writeByteArray(value, TYPE_BYTE_ARRAY);
473             return this;
474         }
475 
writeByteArray(@onNull final byte[] value, final byte typeId)476         private void writeByteArray(@NonNull final byte[] value, final byte typeId) {
477             writeTypeId(typeId);
478             final int numBytes = value.length;
479             mPos += mBuffer.putInt(mPos, numBytes);
480             mPos += mBuffer.putByteArray(mPos, value);
481             mNumElements++;
482         }
483 
484         /**
485          * Write an attribution chain field to this StatsEvent.
486          *
487          * The sizes of uids and tags must be equal. The AttributionNode at position i is
488          * made up of uids[i] and tags[i].
489          *
490          * @param uids array of uids in the attribution nodes.
491          * @param tags array of tags in the attribution nodes.
492          **/
493         @NonNull
writeAttributionChain( @onNull final int[] uids, @NonNull final String[] tags)494         public Builder writeAttributionChain(
495                 @NonNull final int[] uids, @NonNull final String[] tags) {
496             final byte numUids = (byte) uids.length;
497             final byte numTags = (byte) tags.length;
498 
499             if (numUids != numTags) {
500                 mErrorMask |= ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL;
501             } else if (numUids > MAX_ATTRIBUTION_NODES) {
502                 mErrorMask |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG;
503             } else {
504                 // Write attribution chain typeId byte, followed by 1-byte representation of
505                 // number of attribution nodes, followed by encoding of each attribution node.
506                 writeTypeId(TYPE_ATTRIBUTION_CHAIN);
507                 mPos += mBuffer.putByte(mPos, numUids);
508                 for (int i = 0; i < numUids; i++) {
509                     // Each uid is encoded as 4-byte representation of its int value.
510                     mPos += mBuffer.putInt(mPos, uids[i]);
511 
512                     // Each tag is encoded as 4-byte representation of number of bytes in its
513                     // UTF-8 encoding, followed by the actual UTF-8 bytes.
514                     final byte[] tagBytes = stringToBytes(tags[i]);
515                     mPos += mBuffer.putInt(mPos, tagBytes.length);
516                     mPos += mBuffer.putByteArray(mPos, tagBytes);
517                 }
518                 mNumElements++;
519             }
520             return this;
521         }
522 
523         /**
524          * Write KeyValuePairsAtom entries to this StatsEvent.
525          *
526          * @param intMap Integer key-value pairs.
527          * @param longMap Long key-value pairs.
528          * @param stringMap String key-value pairs.
529          * @param floatMap Float key-value pairs.
530          **/
531         @NonNull
writeKeyValuePairs( @ullable final SparseIntArray intMap, @Nullable final SparseLongArray longMap, @Nullable final SparseArray<String> stringMap, @Nullable final SparseArray<Float> floatMap)532         public Builder writeKeyValuePairs(
533                 @Nullable final SparseIntArray intMap,
534                 @Nullable final SparseLongArray longMap,
535                 @Nullable final SparseArray<String> stringMap,
536                 @Nullable final SparseArray<Float> floatMap) {
537             final int intMapSize = null == intMap ? 0 : intMap.size();
538             final int longMapSize = null == longMap ? 0 : longMap.size();
539             final int stringMapSize = null == stringMap ? 0 : stringMap.size();
540             final int floatMapSize = null == floatMap ? 0 : floatMap.size();
541             final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize;
542 
543             if (totalCount > MAX_KEY_VALUE_PAIRS) {
544                 mErrorMask |= ERROR_TOO_MANY_KEY_VALUE_PAIRS;
545             } else {
546                 writeTypeId(TYPE_KEY_VALUE_PAIRS);
547                 mPos += mBuffer.putByte(mPos, (byte) totalCount);
548 
549                 for (int i = 0; i < intMapSize; i++) {
550                     final int key = intMap.keyAt(i);
551                     final int value = intMap.valueAt(i);
552                     mPos += mBuffer.putInt(mPos, key);
553                     writeTypeId(TYPE_INT);
554                     mPos += mBuffer.putInt(mPos, value);
555                 }
556 
557                 for (int i = 0; i < longMapSize; i++) {
558                     final int key = longMap.keyAt(i);
559                     final long value = longMap.valueAt(i);
560                     mPos += mBuffer.putInt(mPos, key);
561                     writeTypeId(TYPE_LONG);
562                     mPos += mBuffer.putLong(mPos, value);
563                 }
564 
565                 for (int i = 0; i < stringMapSize; i++) {
566                     final int key = stringMap.keyAt(i);
567                     final String value = stringMap.valueAt(i);
568                     mPos += mBuffer.putInt(mPos, key);
569                     writeTypeId(TYPE_STRING);
570                     final byte[] valueBytes = stringToBytes(value);
571                     mPos += mBuffer.putInt(mPos, valueBytes.length);
572                     mPos += mBuffer.putByteArray(mPos, valueBytes);
573                 }
574 
575                 for (int i = 0; i < floatMapSize; i++) {
576                     final int key = floatMap.keyAt(i);
577                     final float value = floatMap.valueAt(i);
578                     mPos += mBuffer.putInt(mPos, key);
579                     writeTypeId(TYPE_FLOAT);
580                     mPos += mBuffer.putFloat(mPos, value);
581                 }
582 
583                 mNumElements++;
584             }
585 
586             return this;
587         }
588 
589         /**
590          * Write a repeated boolean field to this StatsEvent.
591          *
592          * The list size must not exceed 127. Otherwise, the array isn't written
593          * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
594          * StatsEvent errors field.
595          *
596          * @param elements array of booleans.
597          **/
598         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
599         @NonNull
writeBooleanArray(@onNull final boolean[] elements)600         public Builder writeBooleanArray(@NonNull final boolean[] elements) {
601             final byte numElements = (byte)elements.length;
602 
603             if (writeArrayInfo(numElements, TYPE_BOOLEAN)) {
604                 // Write encoding of each element.
605                 for (int i = 0; i < numElements; i++) {
606                     mPos += mBuffer.putBoolean(mPos, elements[i]);
607                 }
608                 mNumElements++;
609             }
610             return this;
611         }
612 
613         /**
614          * Write a repeated int field to this StatsEvent.
615          *
616          * The list size must not exceed 127. Otherwise, the array isn't written
617          * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
618          * StatsEvent errors field.
619          *
620          * @param elements array of ints.
621          **/
622         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
623         @NonNull
writeIntArray(@onNull final int[] elements)624         public Builder writeIntArray(@NonNull final int[] elements) {
625             final byte numElements = (byte)elements.length;
626 
627             if (writeArrayInfo(numElements, TYPE_INT)) {
628               // Write encoding of each element.
629               for (int i = 0; i < numElements; i++) {
630                 mPos += mBuffer.putInt(mPos, elements[i]);
631                 }
632                 mNumElements++;
633             }
634             return this;
635         }
636 
637         /**
638          * Write a repeated long field to this StatsEvent.
639          *
640          * The list size must not exceed 127. Otherwise, the array isn't written
641          * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
642          * StatsEvent errors field.
643          *
644          * @param elements array of longs.
645          **/
646         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
647         @NonNull
writeLongArray(@onNull final long[] elements)648         public Builder writeLongArray(@NonNull final long[] elements) {
649             final byte numElements = (byte)elements.length;
650 
651             if (writeArrayInfo(numElements, TYPE_LONG)) {
652                 // Write encoding of each element.
653                 for (int i = 0; i < numElements; i++) {
654                     mPos += mBuffer.putLong(mPos, elements[i]);
655                 }
656                 mNumElements++;
657             }
658             return this;
659         }
660 
661         /**
662          * Write a repeated float field to this StatsEvent.
663          *
664          * The list size must not exceed 127. Otherwise, the array isn't written
665          * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
666          * StatsEvent errors field.
667          *
668          * @param elements array of floats.
669          **/
670         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
671         @NonNull
writeFloatArray(@onNull final float[] elements)672         public Builder writeFloatArray(@NonNull final float[] elements) {
673             final byte numElements = (byte)elements.length;
674 
675             if (writeArrayInfo(numElements, TYPE_FLOAT)) {
676                 // Write encoding of each element.
677                 for (int i = 0; i < numElements; i++) {
678                   mPos += mBuffer.putFloat(mPos, elements[i]);
679                 }
680                 mNumElements++;
681             }
682             return this;
683         }
684 
685         /**
686          * Write a repeated string field to this StatsEvent.
687          *
688          * The list size must not exceed 127. Otherwise, the array isn't written
689          * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the
690          * StatsEvent errors field.
691          *
692          * @param elements array of strings.
693          **/
694         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
695         @NonNull
writeStringArray(@onNull final String[] elements)696         public Builder writeStringArray(@NonNull final String[] elements) {
697             final byte numElements = (byte)elements.length;
698 
699             if (writeArrayInfo(numElements, TYPE_STRING)) {
700                 // Write encoding of each element.
701                 for (int i = 0; i < numElements; i++) {
702                     final byte[] elementBytes = stringToBytes(elements[i]);
703                     mPos += mBuffer.putInt(mPos, elementBytes.length);
704                     mPos += mBuffer.putByteArray(mPos, elementBytes);
705                 }
706                 mNumElements++;
707             }
708             return this;
709         }
710 
711         /**
712          * Write a boolean annotation for the last field written.
713          **/
714         @NonNull
addBooleanAnnotation( final byte annotationId, final boolean value)715         public Builder addBooleanAnnotation(
716                 final byte annotationId, final boolean value) {
717             // Ensure there's a field written to annotate.
718             if (mNumElements < 2) {
719                 mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
720             } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
721                 mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
722             } else {
723                 mPos += mBuffer.putByte(mPos, annotationId);
724                 mPos += mBuffer.putByte(mPos, TYPE_BOOLEAN);
725                 mPos += mBuffer.putBoolean(mPos, value);
726                 mCurrentAnnotationCount++;
727                 writeAnnotationCount();
728             }
729 
730             return this;
731         }
732 
733         /**
734          * Write an integer annotation for the last field written.
735          **/
736         @NonNull
addIntAnnotation(final byte annotationId, final int value)737         public Builder addIntAnnotation(final byte annotationId, final int value) {
738             if (mNumElements < 2) {
739                 mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
740             } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
741                 mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
742             } else {
743                 mPos += mBuffer.putByte(mPos, annotationId);
744                 mPos += mBuffer.putByte(mPos, TYPE_INT);
745                 mPos += mBuffer.putInt(mPos, value);
746                 mCurrentAnnotationCount++;
747                 writeAnnotationCount();
748             }
749 
750             return this;
751         }
752 
753         /**
754          * Indicates to reuse Buffer's byte array as the underlying payload in StatsEvent.
755          * This should be called for pushed events to reduce memory allocations and garbage
756          * collections.
757          **/
758         @NonNull
usePooledBuffer()759         public Builder usePooledBuffer() {
760             mUsePooledBuffer = true;
761             mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos);
762             return this;
763         }
764 
765         /**
766          * Builds a StatsEvent object with values entered in this Builder.
767          **/
768         @NonNull
build()769         public StatsEvent build() {
770             if (0L == mTimestampNs) {
771                 mErrorMask |= ERROR_NO_TIMESTAMP;
772             }
773             if (0 == mAtomId) {
774                 mErrorMask |= ERROR_NO_ATOM_ID;
775             }
776             if (mBuffer.hasOverflowed()) {
777                 mErrorMask |= ERROR_OVERFLOW;
778             }
779             if (mNumElements > MAX_NUM_ELEMENTS) {
780                 mErrorMask |= ERROR_TOO_MANY_FIELDS;
781             }
782 
783             if (0 == mErrorMask) {
784                 mBuffer.putByte(POS_NUM_ELEMENTS, (byte) mNumElements);
785             } else {
786                 // Write atom id and error mask. Overwrite any annotations for atom Id.
787                 mPos = POS_ATOM_ID;
788                 mPos += mBuffer.putByte(mPos, TYPE_INT);
789                 mPos += mBuffer.putInt(mPos, mAtomId);
790                 mPos += mBuffer.putByte(mPos, TYPE_ERRORS);
791                 mPos += mBuffer.putInt(mPos, mErrorMask);
792                 mBuffer.putByte(POS_NUM_ELEMENTS, (byte) 3);
793             }
794 
795             final int size = mPos;
796 
797             if (mUsePooledBuffer) {
798                 return new StatsEvent(mAtomId, mBuffer, mBuffer.getBytes(), size);
799             } else {
800                 // Create a copy of the buffer with the required number of bytes.
801                 final byte[] payload = new byte[size];
802                 System.arraycopy(mBuffer.getBytes(), 0, payload, 0, size);
803 
804                 // Return Buffer instance to the pool.
805                 mBuffer.release();
806 
807                 return new StatsEvent(mAtomId, null, payload, size);
808             }
809         }
810 
writeTypeId(final byte typeId)811         private void writeTypeId(final byte typeId) {
812             mPosLastField = mPos;
813             mLastType = typeId;
814             mCurrentAnnotationCount = 0;
815             final byte encodedId = (byte) (typeId & 0x0F);
816             mPos += mBuffer.putByte(mPos, encodedId);
817         }
818 
writeAnnotationCount()819         private void writeAnnotationCount() {
820             // Use first 4 bits for annotation count and last 4 bits for typeId.
821             final byte encodedId = (byte) ((mCurrentAnnotationCount << 4) | (mLastType & 0x0F));
822             mBuffer.putByte(mPosLastField, encodedId);
823         }
824 
825         @NonNull
stringToBytes(@ullable final String value)826         private static byte[] stringToBytes(@Nullable final String value) {
827             return (null == value ? "" : value).getBytes(UTF_8);
828         }
829 
writeArrayInfo(final byte numElements, final byte elementTypeId)830         private boolean writeArrayInfo(final byte numElements,
831                                        final byte elementTypeId) {
832             if (numElements > MAX_NUM_ELEMENTS) {
833                 mErrorMask |= ERROR_LIST_TOO_LONG;
834                 return false;
835             }
836             // Write list typeId byte, 1-byte representation of number of
837             // elements, and element typeId byte.
838             writeTypeId(TYPE_LIST);
839             mPos += mBuffer.putByte(mPos, numElements);
840             // Write element typeId byte without setting mPosLastField and mLastType (i.e. don't use
841             // #writeTypeId)
842             final byte encodedId = (byte) (elementTypeId & 0x0F);
843             mPos += mBuffer.putByte(mPos, encodedId);
844             return true;
845         }
846     }
847 
848     private static final class Buffer {
849         private static Object sLock = new Object();
850 
851         @GuardedBy("sLock")
852         private static Buffer sPool;
853 
854         private byte[] mBytes;
855         private boolean mOverflow = false;
856         private int mMaxSize = MAX_PULL_PAYLOAD_SIZE;
857 
858         @NonNull
obtain()859         private static Buffer obtain() {
860             final Buffer buffer;
861             synchronized (sLock) {
862                 buffer = null == sPool ? new Buffer() : sPool;
863                 sPool = null;
864             }
865             buffer.reset();
866             return buffer;
867         }
868 
Buffer()869         private Buffer() {
870             final ByteBuffer tempBuffer = ByteBuffer.allocateDirect(MAX_PUSH_PAYLOAD_SIZE);
871             mBytes = tempBuffer.hasArray() ? tempBuffer.array() : new byte [MAX_PUSH_PAYLOAD_SIZE];
872         }
873 
874         @NonNull
getBytes()875         private byte[] getBytes() {
876             return mBytes;
877         }
878 
release()879         private void release() {
880             // Recycle this Buffer if its size is MAX_PUSH_PAYLOAD_SIZE or under.
881             if (mMaxSize <= MAX_PUSH_PAYLOAD_SIZE) {
882                 synchronized (sLock) {
883                     if (null == sPool) {
884                         sPool = this;
885                     }
886                 }
887             }
888         }
889 
reset()890         private void reset() {
891             mOverflow = false;
892             mMaxSize = MAX_PULL_PAYLOAD_SIZE;
893         }
894 
setMaxSize(final int maxSize, final int numBytesWritten)895         private void setMaxSize(final int maxSize, final int numBytesWritten) {
896             mMaxSize = maxSize;
897             if (numBytesWritten > maxSize) {
898                 mOverflow = true;
899             }
900         }
901 
hasOverflowed()902         private boolean hasOverflowed() {
903             return mOverflow;
904         }
905 
906         /**
907          * Checks for available space in the byte array.
908          *
909          * @param index starting position in the buffer to start the check.
910          * @param numBytes number of bytes to check from index.
911          * @return true if space is available, false otherwise.
912          **/
hasEnoughSpace(final int index, final int numBytes)913         private boolean hasEnoughSpace(final int index, final int numBytes) {
914             final int totalBytesNeeded = index + numBytes;
915 
916             if (totalBytesNeeded > mMaxSize) {
917                 mOverflow = true;
918                 return false;
919             }
920 
921             // Expand buffer if needed.
922             if (mBytes.length < mMaxSize && totalBytesNeeded > mBytes.length) {
923                 int newSize = mBytes.length;
924                 do {
925                     newSize *= 2;
926                 } while (newSize <= totalBytesNeeded);
927 
928                 if (newSize > mMaxSize) {
929                     newSize = mMaxSize;
930                 }
931 
932                 mBytes = Arrays.copyOf(mBytes, newSize);
933             }
934 
935             return true;
936         }
937 
938         /**
939          * Writes a byte into the buffer.
940          *
941          * @param index position in the buffer where the byte is written.
942          * @param value the byte to write.
943          * @return number of bytes written to buffer from this write operation.
944          **/
putByte(final int index, final byte value)945         private int putByte(final int index, final byte value) {
946             if (hasEnoughSpace(index, Byte.BYTES)) {
947                 mBytes[index] = (byte) (value);
948                 return Byte.BYTES;
949             }
950             return 0;
951         }
952 
953         /**
954          * Writes a boolean into the buffer.
955          *
956          * @param index position in the buffer where the boolean is written.
957          * @param value the boolean to write.
958          * @return number of bytes written to buffer from this write operation.
959          **/
putBoolean(final int index, final boolean value)960         private int putBoolean(final int index, final boolean value) {
961             return putByte(index, (byte) (value ? 1 : 0));
962         }
963 
964         /**
965          * Writes an integer into the buffer.
966          *
967          * @param index position in the buffer where the integer is written.
968          * @param value the integer to write.
969          * @return number of bytes written to buffer from this write operation.
970          **/
putInt(final int index, final int value)971         private int putInt(final int index, final int value) {
972             if (hasEnoughSpace(index, Integer.BYTES)) {
973                 // Use little endian byte order.
974                 mBytes[index] = (byte) (value);
975                 mBytes[index + 1] = (byte) (value >> 8);
976                 mBytes[index + 2] = (byte) (value >> 16);
977                 mBytes[index + 3] = (byte) (value >> 24);
978                 return Integer.BYTES;
979             }
980             return 0;
981         }
982 
983         /**
984          * Writes a long into the buffer.
985          *
986          * @param index position in the buffer where the long is written.
987          * @param value the long to write.
988          * @return number of bytes written to buffer from this write operation.
989          **/
putLong(final int index, final long value)990         private int putLong(final int index, final long value) {
991             if (hasEnoughSpace(index, Long.BYTES)) {
992                 // Use little endian byte order.
993                 mBytes[index] = (byte) (value);
994                 mBytes[index + 1] = (byte) (value >> 8);
995                 mBytes[index + 2] = (byte) (value >> 16);
996                 mBytes[index + 3] = (byte) (value >> 24);
997                 mBytes[index + 4] = (byte) (value >> 32);
998                 mBytes[index + 5] = (byte) (value >> 40);
999                 mBytes[index + 6] = (byte) (value >> 48);
1000                 mBytes[index + 7] = (byte) (value >> 56);
1001                 return Long.BYTES;
1002             }
1003             return 0;
1004         }
1005 
1006         /**
1007          * Writes a float into the buffer.
1008          *
1009          * @param index position in the buffer where the float is written.
1010          * @param value the float to write.
1011          * @return number of bytes written to buffer from this write operation.
1012          **/
putFloat(final int index, final float value)1013         private int putFloat(final int index, final float value) {
1014             return putInt(index, Float.floatToIntBits(value));
1015         }
1016 
1017         /**
1018          * Copies a byte array into the buffer.
1019          *
1020          * @param index position in the buffer where the byte array is copied.
1021          * @param value the byte array to copy.
1022          * @return number of bytes written to buffer from this write operation.
1023          **/
putByteArray(final int index, @NonNull final byte[] value)1024         private int putByteArray(final int index, @NonNull final byte[] value) {
1025             final int numBytes = value.length;
1026             if (hasEnoughSpace(index, numBytes)) {
1027                 System.arraycopy(value, 0, mBytes, index, numBytes);
1028                 return numBytes;
1029             }
1030             return 0;
1031         }
1032     }
1033 }
1034