Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2a6214ca authored by Muhammad Qureshi's avatar Muhammad Qureshi Committed by android-build-merger
Browse files

Merge "First draft of StatsEvent.java"

am: 4e2e5a96

Change-Id: I0419c99c8eba17b4c6f9733c09019da88db43939
parents acf7918f 4e2e5a96
Loading
Loading
Loading
Loading
+331 −0
Original line number Original line Diff line number Diff line
/*
 * 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);
    }
}