blob: 24b4f73e3d057c38d4f7448a5031be8aed8b1427 [file] [log] [blame]
/*
* 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 stansard 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();
}
/** @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 mBuffer.getInt();
case LONG:
if (mBuffer.remaining() < 8) return null;
return 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;
}