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