| /* |
| * Copyright (C) 2007 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 com.google.android.collect.Lists; |
| |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| |
| /** |
| * {@hide} |
| * Dynamically defined (in terms of event types), space efficient (i.e. "tight") event logging |
| * to help instrument code for large scale stability and performance monitoring. |
| * |
| * Note that this class contains all static methods. This is done for efficiency reasons. |
| * |
| * Events for the event log are self-describing binary data structures. They start with a 20 byte |
| * header (generated automatically) which contains all of the following in order: |
| * |
| * <ul> |
| * <li> Payload length: 2 bytes - length of the non-header portion </li> |
| * <li> Padding: 2 bytes - no meaning at this time </li> |
| * <li> Timestamp: |
| * <ul> |
| * <li> Seconds: 4 bytes - seconds since Epoch </li> |
| * <li> Nanoseconds: 4 bytes - plus extra nanoseconds </li> |
| * </ul></li> |
| * <li> Process ID: 4 bytes - matching {@link android.os.Process#myPid} </li> |
| * <li> Thread ID: 4 bytes - matching {@link android.os.Process#myTid} </li> |
| * </li> |
| * </ul> |
| * |
| * The above is followed by a payload, comprised of the following: |
| * <ul> |
| * <li> Tag: 4 bytes - unique integer used to identify a particular event. This number is also |
| * used as a key to map to a string that can be displayed by log reading tools. |
| * </li> |
| * <li> Type: 1 byte - can be either {@link #INT}, {@link #LONG}, {@link #STRING}, |
| * or {@link #LIST}. </li> |
| * <li> Event log value: the size and format of which is one of: |
| * <ul> |
| * <li> INT: 4 bytes </li> |
| * <li> LONG: 8 bytes </li> |
| * <li> STRING: |
| * <ul> |
| * <li> Size of STRING: 4 bytes </li> |
| * <li> The string: n bytes as specified in the size fields above. </li> |
| * </ul></li> |
| * <li> {@link List LIST}: |
| * <ul> |
| * <li> Num items: 1 byte </li> |
| * <li> N value payloads, where N is the number of items specified above. </li> |
| * </ul></li> |
| * </ul> |
| * </li> |
| * <li> '\n': 1 byte - an automatically generated newline, used to help detect and recover from log |
| * corruption and enable standard unix tools like grep, tail and wc to operate |
| * on event logs. </li> |
| * </ul> |
| * |
| * Note that all output is done in the endian-ness of the device (as determined |
| * by {@link ByteOrder#nativeOrder()}). |
| */ |
| |
| public class EventLog { |
| |
| // Value types |
| public static final byte INT = 0; |
| public static final byte LONG = 1; |
| public static final byte STRING = 2; |
| public static final byte LIST = 3; |
| |
| /** |
| * An immutable tuple used to log a heterogeneous set of loggable items. |
| * The items can be Integer, Long, String, or {@link List}. |
| * The maximum number of items is 127 |
| */ |
| public static final class List { |
| private Object[] mItems; |
| |
| /** |
| * Get a particular tuple item |
| * @param pos The position of the item in the tuple |
| */ |
| public final Object getItem(int pos) { |
| return mItems[pos]; |
| } |
| |
| /** |
| * Get the number of items in the tuple. |
| */ |
| public final byte getNumItems() { |
| return (byte) mItems.length; |
| } |
| |
| /** |
| * Create a new tuple. |
| * @param items The items to create the tuple with, as varargs. |
| * @throws IllegalArgumentException if the arguments are too few (0), |
| * too many, or aren't loggable types. |
| */ |
| public List(Object... items) throws IllegalArgumentException { |
| if (items.length > Byte.MAX_VALUE) { |
| throw new IllegalArgumentException( |
| "A List must have fewer than " |
| + Byte.MAX_VALUE + " items in it."); |
| } |
| if (items.length < 1) { |
| throw new IllegalArgumentException( |
| "A List must have at least one item in it."); |
| } |
| for (int i = 0; i < items.length; i++) { |
| final Object item = items[i]; |
| if (item == null) { |
| // Would be nice to be able to write null strings... |
| items[i] = ""; |
| } else if (!(item instanceof List || |
| item instanceof String || |
| item instanceof Integer || |
| item instanceof Long)) { |
| throw new IllegalArgumentException( |
| "Attempt to create a List with illegal item type."); |
| } |
| } |
| this.mItems = items; |
| } |
| } |
| |
| /** |
| * A previously logged event read from the logs. |
| */ |
| public static final class Event { |
| private final ByteBuffer mBuffer; |
| |
| // Layout of event log entry received from kernel. |
| private static final int LENGTH_OFFSET = 0; |
| private static final int PROCESS_OFFSET = 4; |
| private static final int THREAD_OFFSET = 8; |
| private static final int SECONDS_OFFSET = 12; |
| private static final int NANOSECONDS_OFFSET = 16; |
| |
| private static final int PAYLOAD_START = 20; |
| private static final int TAG_OFFSET = 20; |
| private static final int DATA_START = 24; |
| |
| /** @param data containing event, read from the system */ |
| public Event(byte[] data) { |
| mBuffer = ByteBuffer.wrap(data); |
| mBuffer.order(ByteOrder.nativeOrder()); |
| } |
| |
| public int getProcessId() { |
| return mBuffer.getInt(PROCESS_OFFSET); |
| } |
| |
| public int getThreadId() { |
| return mBuffer.getInt(THREAD_OFFSET); |
| } |
| |
| public long getTimeNanos() { |
| return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l |
| + mBuffer.getInt(NANOSECONDS_OFFSET); |
| } |
| |
| public int getTag() { |
| return mBuffer.getInt(TAG_OFFSET); |
| } |
| |
| /** @return one of Integer, Long, String, or List. */ |
| public synchronized Object getData() { |
| mBuffer.limit(PAYLOAD_START + mBuffer.getShort(LENGTH_OFFSET)); |
| mBuffer.position(DATA_START); // Just after the tag. |
| return decodeObject(); |
| } |
| |
| public byte[] getRawData() { |
| return mBuffer.array(); |
| } |
| |
| /** @return the loggable item at the current position in mBuffer. */ |
| private Object decodeObject() { |
| if (mBuffer.remaining() < 1) return null; |
| switch (mBuffer.get()) { |
| case INT: |
| if (mBuffer.remaining() < 4) return null; |
| return (Integer) mBuffer.getInt(); |
| |
| case LONG: |
| if (mBuffer.remaining() < 8) return null; |
| return (Long) mBuffer.getLong(); |
| |
| case STRING: |
| try { |
| if (mBuffer.remaining() < 4) return null; |
| int length = mBuffer.getInt(); |
| if (length < 0 || mBuffer.remaining() < length) return null; |
| int start = mBuffer.position(); |
| mBuffer.position(start + length); |
| return new String(mBuffer.array(), start, length, "UTF-8"); |
| } catch (UnsupportedEncodingException e) { |
| throw new RuntimeException(e); // UTF-8 is guaranteed. |
| } |
| |
| case LIST: |
| if (mBuffer.remaining() < 1) return null; |
| int length = mBuffer.get(); |
| if (length <= 0) return null; |
| Object[] array = new Object[length]; |
| for (int i = 0; i < length; ++i) { |
| array[i] = decodeObject(); |
| if (array[i] == null) return null; |
| } |
| return new List(array); |
| |
| default: |
| return null; |
| } |
| } |
| } |
| |
| // We assume that the native methods deal with any concurrency issues. |
| |
| /** |
| * Send an event log message. |
| * @param tag An event identifer |
| * @param value A value to log |
| * @return The number of bytes written |
| */ |
| public static native int writeEvent(int tag, int value); |
| |
| /** |
| * Send an event log message. |
| * @param tag An event identifer |
| * @param value A value to log |
| * @return The number of bytes written |
| */ |
| public static native int writeEvent(int tag, long value); |
| |
| /** |
| * Send an event log message. |
| * @param tag An event identifer |
| * @param str A value to log |
| * @return The number of bytes written |
| */ |
| public static native int writeEvent(int tag, String str); |
| |
| /** |
| * Send an event log message. |
| * @param tag An event identifer |
| * @param list A {@link List} to log |
| * @return The number of bytes written |
| */ |
| public static native int writeEvent(int tag, List list); |
| |
| /** |
| * Send an event log message. |
| * @param tag An event identifer |
| * @param list A list of values to log |
| * @return The number of bytes written |
| */ |
| public static int writeEvent(int tag, Object... list) { |
| return writeEvent(tag, new List(list)); |
| } |
| |
| /** |
| * Read events from the log, filtered by type. |
| * @param tags to search for |
| * @param output container to add events into |
| * @throws IOException if something goes wrong reading events |
| */ |
| public static native void readEvents(int[] tags, Collection<Event> output) |
| throws IOException; |
| |
| /** |
| * Read events from a file. |
| * @param path to read from |
| * @param output container to add events into |
| * @throws IOException if something goes wrong reading events |
| */ |
| public static native void readEvents(String path, Collection<Event> output) |
| throws IOException; |
| } |