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