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