Merge "First draft of StatsEvent.java"
This commit is contained in:
331
core/java/android/util/StatsEvent.java
Normal file
331
core/java/android/util/StatsEvent.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user