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

Commit 4e2e5a96 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "First draft of StatsEvent.java"

parents 3863c17f ffca2a35
Loading
Loading
Loading
Loading
+331 −0
Original line number 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);
    }
}