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