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