diff options
| -rw-r--r-- | core/java/android/util/StatsEvent.java | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/core/java/android/util/StatsEvent.java b/core/java/android/util/StatsEvent.java new file mode 100644 index 000000000000..91a5ec0303f7 --- /dev/null +++ b/core/java/android/util/StatsEvent.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.annotations.GuardedBy; + +/** + * StatsEvent builds and stores the buffer sent over the statsd socket. + * This class defines and encapsulates the socket protocol. + * @hide + **/ +public final class StatsEvent implements AutoCloseable { + private static final int POS_NUM_ELEMENTS = 1; + private static final int POS_TIMESTAMP = POS_NUM_ELEMENTS + 1; + + private static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068; + + // Max payload size is 4 KB less 4 bytes which are reserved for statsEventTag. + // See android_util_StatsLog.cpp. + private static final int MAX_EVENT_PAYLOAD = LOGGER_ENTRY_MAX_PAYLOAD - 4; + + private static final byte INT_TYPE = 0; + private static final byte LONG_TYPE = 1; + private static final byte STRING_TYPE = 2; + private static final byte LIST_TYPE = 3; + private static final byte FLOAT_TYPE = 4; + + private static final int INT_TYPE_SIZE = 5; + private static final int FLOAT_TYPE_SIZE = 5; + private static final int LONG_TYPE_SIZE = 9; + + private static final int STRING_TYPE_OVERHEAD = 5; + private static final int LIST_TYPE_OVERHEAD = 2; + + public static final int SUCCESS = 0; + public static final int ERROR_BUFFER_LIMIT_EXCEEDED = -1; + public static final int ERROR_NO_TIMESTAMP = -2; + public static final int ERROR_TIMESTAMP_ALREADY_WRITTEN = -3; + public static final int ERROR_NO_ATOM_ID = -4; + public static final int ERROR_ATOM_ID_ALREADY_WRITTEN = -5; + public static final int ERROR_UID_TAG_COUNT_MISMATCH = -6; + + private static Object sLock = new Object(); + + @GuardedBy("sLock") + private static StatsEvent sPool; + + private final byte[] mBuffer = new byte[MAX_EVENT_PAYLOAD]; + private int mPos; + private int mNumElements; + private int mAtomId; + + private StatsEvent() { + // Write LIST_TYPE to buffer + mBuffer[0] = LIST_TYPE; + reset(); + } + + private void reset() { + // Reset state. + mPos = POS_TIMESTAMP; + mNumElements = 0; + mAtomId = 0; + } + + /** + * Returns a StatsEvent object from the pool. + **/ + @NonNull + public static StatsEvent obtain() { + final StatsEvent statsEvent; + synchronized (sLock) { + statsEvent = null == sPool ? new StatsEvent() : sPool; + sPool = null; + } + statsEvent.reset(); + return statsEvent; + } + + @Override + public void close() { + synchronized (sLock) { + if (null == sPool) { + sPool = this; + } + } + } + + /** + * Writes the event timestamp to the buffer. + **/ + public int writeTimestampNs(final long timestampNs) { + if (hasTimestamp()) { + return ERROR_TIMESTAMP_ALREADY_WRITTEN; + } + return writeLong(timestampNs); + } + + private boolean hasTimestamp() { + return mPos > POS_TIMESTAMP; + } + + private boolean hasAtomId() { + return mAtomId != 0; + } + + /** + * Writes the atom id to the buffer. + **/ + public int writeAtomId(final int atomId) { + if (!hasTimestamp()) { + return ERROR_NO_TIMESTAMP; + } else if (hasAtomId()) { + return ERROR_ATOM_ID_ALREADY_WRITTEN; + } + + final int writeResult = writeInt(atomId); + if (SUCCESS == writeResult) { + mAtomId = atomId; + } + return writeResult; + } + + /** + * Appends the given int to the StatsEvent buffer. + **/ + public int writeInt(final int value) { + if (!hasTimestamp()) { + return ERROR_NO_TIMESTAMP; + } else if (!hasAtomId()) { + return ERROR_NO_ATOM_ID; + } else if (mPos + INT_TYPE_SIZE > MAX_EVENT_PAYLOAD) { + return ERROR_BUFFER_LIMIT_EXCEEDED; + } + + mBuffer[mPos] = INT_TYPE; + copyInt(mBuffer, mPos + 1, value); + mPos += INT_TYPE_SIZE; + mNumElements++; + return SUCCESS; + } + + /** + * Appends the given long to the StatsEvent buffer. + **/ + public int writeLong(final long value) { + if (!hasTimestamp()) { + return ERROR_NO_TIMESTAMP; + } else if (!hasAtomId()) { + return ERROR_NO_ATOM_ID; + } else if (mPos + LONG_TYPE_SIZE > MAX_EVENT_PAYLOAD) { + return ERROR_BUFFER_LIMIT_EXCEEDED; + } + + mBuffer[mPos] = LONG_TYPE; + copyLong(mBuffer, mPos + 1, value); + mPos += LONG_TYPE_SIZE; + mNumElements++; + return SUCCESS; + } + + /** + * Appends the given float to the StatsEvent buffer. + **/ + public int writeFloat(final float value) { + if (!hasTimestamp()) { + return ERROR_NO_TIMESTAMP; + } else if (!hasAtomId()) { + return ERROR_NO_ATOM_ID; + } else if (mPos + FLOAT_TYPE_SIZE > MAX_EVENT_PAYLOAD) { + return ERROR_BUFFER_LIMIT_EXCEEDED; + } + + mBuffer[mPos] = FLOAT_TYPE; + copyInt(mBuffer, mPos + 1, Float.floatToIntBits(value)); + mPos += FLOAT_TYPE_SIZE; + mNumElements++; + return SUCCESS; + } + + /** + * Appends the given boolean to the StatsEvent buffer. + **/ + public int writeBoolean(final boolean value) { + return writeInt(value ? 1 : 0); + } + + /** + * Appends the given byte array to the StatsEvent buffer. + **/ + public int writeByteArray(@NonNull final byte[] value) { + if (!hasTimestamp()) { + return ERROR_NO_TIMESTAMP; + } else if (!hasAtomId()) { + return ERROR_NO_ATOM_ID; + } else if (mPos + STRING_TYPE_OVERHEAD + value.length > MAX_EVENT_PAYLOAD) { + return ERROR_BUFFER_LIMIT_EXCEEDED; + } + + mBuffer[mPos] = STRING_TYPE; + copyInt(mBuffer, mPos + 1, value.length); + System.arraycopy(value, 0, mBuffer, mPos + STRING_TYPE_OVERHEAD, value.length); + mPos += STRING_TYPE_OVERHEAD + value.length; + mNumElements++; + return SUCCESS; + } + + /** + * Appends the given String to the StatsEvent buffer. + **/ + public int writeString(@NonNull final String value) { + final byte[] valueBytes = stringToBytes(value); + return writeByteArray(valueBytes); + } + + /** + * Appends the AttributionNode specified as array of uids and array of tags. + **/ + public int writeAttributionNode(@NonNull final int[] uids, @NonNull final String[] tags) { + if (!hasTimestamp()) { + return ERROR_NO_TIMESTAMP; + } else if (!hasAtomId()) { + return ERROR_NO_ATOM_ID; + } else if (mPos + LIST_TYPE_OVERHEAD > MAX_EVENT_PAYLOAD) { + return ERROR_BUFFER_LIMIT_EXCEEDED; + } + + final int numTags = tags.length; + final int numUids = uids.length; + if (numTags != numUids) { + return ERROR_UID_TAG_COUNT_MISMATCH; + } + + int pos = mPos; + mBuffer[pos] = LIST_TYPE; + mBuffer[pos + 1] = (byte) numTags; + pos += LIST_TYPE_OVERHEAD; + for (int i = 0; i < numTags; i++) { + final byte[] tagBytes = stringToBytes(tags[i]); + + if (pos + LIST_TYPE_OVERHEAD + INT_TYPE_SIZE + + STRING_TYPE_OVERHEAD + tagBytes.length > MAX_EVENT_PAYLOAD) { + return ERROR_BUFFER_LIMIT_EXCEEDED; + } + + mBuffer[pos] = LIST_TYPE; + mBuffer[pos + 1] = 2; + pos += LIST_TYPE_OVERHEAD; + mBuffer[pos] = INT_TYPE; + copyInt(mBuffer, pos + 1, uids[i]); + pos += INT_TYPE_SIZE; + mBuffer[pos] = STRING_TYPE; + copyInt(mBuffer, pos + 1, tagBytes.length); + System.arraycopy(tagBytes, 0, mBuffer, pos + STRING_TYPE_OVERHEAD, tagBytes.length); + pos += STRING_TYPE_OVERHEAD + tagBytes.length; + } + mPos = pos; + mNumElements++; + return SUCCESS; + } + + /** + * Returns the byte array containing data in the statsd socket format. + * @hide + **/ + @NonNull + public byte[] getBuffer() { + // Encode number of elements in the buffer. + mBuffer[POS_NUM_ELEMENTS] = (byte) mNumElements; + return mBuffer; + } + + /** + * Returns number of bytes used by the buffer. + * @hide + **/ + public int size() { + return mPos; + } + + /** + * Getter for atom id. + * @hide + **/ + public int getAtomId() { + return mAtomId; + } + + @NonNull + private static byte[] stringToBytes(@Nullable final String value) { + return (null == value ? "" : value).getBytes(UTF_8); + } + + // Helper methods for copying primitives + private static void copyInt(@NonNull byte[] buff, int pos, int value) { + buff[pos] = (byte) (value); + buff[pos + 1] = (byte) (value >> 8); + buff[pos + 2] = (byte) (value >> 16); + buff[pos + 3] = (byte) (value >> 24); + } + + private static void copyLong(@NonNull byte[] buff, int pos, long value) { + buff[pos] = (byte) (value); + buff[pos + 1] = (byte) (value >> 8); + buff[pos + 2] = (byte) (value >> 16); + buff[pos + 3] = (byte) (value >> 24); + buff[pos + 4] = (byte) (value >> 32); + buff[pos + 5] = (byte) (value >> 40); + buff[pos + 6] = (byte) (value >> 48); + buff[pos + 7] = (byte) (value >> 56); + } +} |