477 lines
17 KiB
Java
477 lines
17 KiB
Java
![]() |
/*
|
||
|
* 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 android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.annotation.SystemApi;
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
import android.os.Build;
|
||
|
|
||
|
import java.io.BufferedReader;
|
||
|
import java.io.FileReader;
|
||
|
import java.io.IOException;
|
||
|
import java.io.UnsupportedEncodingException;
|
||
|
import java.nio.BufferUnderflowException;
|
||
|
import java.nio.ByteBuffer;
|
||
|
import java.nio.ByteOrder;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.Collection;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.regex.Matcher;
|
||
|
import java.util.regex.Pattern;
|
||
|
|
||
|
/**
|
||
|
* Access to the system diagnostic event record. System diagnostic events are
|
||
|
* used to record certain system-level events (such as garbage collection,
|
||
|
* activity manager state, system watchdogs, and other low level activity),
|
||
|
* which may be automatically collected and analyzed during system development.
|
||
|
*
|
||
|
* <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
|
||
|
* These diagnostic events are for system integrators, not application authors.
|
||
|
*
|
||
|
* <p>Events use integer tag codes corresponding to /system/etc/event-log-tags.
|
||
|
* They carry a payload of one or more int, long, or String values. The
|
||
|
* event-log-tags file defines the payload contents for each type code.
|
||
|
*/
|
||
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
||
|
@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
|
||
|
"com.android.platform.test.ravenwood.nativesubstitution.EventLog_host")
|
||
|
public class EventLog {
|
||
|
/** @hide */ public EventLog() {}
|
||
|
|
||
|
private static final String TAG = "EventLog";
|
||
|
|
||
|
private static final String TAGS_FILE = "/system/etc/event-log-tags";
|
||
|
private static final String COMMENT_PATTERN = "^\\s*(#.*)?$";
|
||
|
private static final String TAG_PATTERN = "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$";
|
||
|
private static HashMap<String, Integer> sTagCodes = null;
|
||
|
private static HashMap<Integer, String> sTagNames = null;
|
||
|
|
||
|
/** A previously logged event read from the logs. Instances are thread safe. */
|
||
|
public static final class Event {
|
||
|
private final ByteBuffer mBuffer;
|
||
|
private Exception mLastWtf;
|
||
|
|
||
|
// Layout of event log entry received from Android logger.
|
||
|
// see system/logging/liblog/include/log/log_read.h
|
||
|
private static final int LENGTH_OFFSET = 0;
|
||
|
private static final int HEADER_SIZE_OFFSET = 2;
|
||
|
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 UID_OFFSET = 24;
|
||
|
|
||
|
// Layout for event log v1 format, v2 and v3 use HEADER_SIZE_OFFSET
|
||
|
private static final int V1_PAYLOAD_START = 20;
|
||
|
private static final int TAG_LENGTH = 4;
|
||
|
|
||
|
// Value types
|
||
|
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;
|
||
|
|
||
|
/** @param data containing event, read from the system */
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
/*package*/ Event(byte[] data) {
|
||
|
mBuffer = ByteBuffer.wrap(data);
|
||
|
mBuffer.order(ByteOrder.nativeOrder());
|
||
|
}
|
||
|
|
||
|
/** @return the process ID which wrote the log entry */
|
||
|
public int getProcessId() {
|
||
|
return mBuffer.getInt(PROCESS_OFFSET);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return the UID which wrote the log entry
|
||
|
* @hide
|
||
|
*/
|
||
|
@SystemApi
|
||
|
public int getUid() {
|
||
|
try {
|
||
|
return mBuffer.getInt(UID_OFFSET);
|
||
|
} catch (IndexOutOfBoundsException e) {
|
||
|
// buffer won't contain the UID if the caller doesn't have permission.
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @return the thread ID which wrote the log entry */
|
||
|
public int getThreadId() {
|
||
|
return mBuffer.getInt(THREAD_OFFSET);
|
||
|
}
|
||
|
|
||
|
/** @return the wall clock time when the entry was written */
|
||
|
public long getTimeNanos() {
|
||
|
return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l
|
||
|
+ mBuffer.getInt(NANOSECONDS_OFFSET);
|
||
|
}
|
||
|
|
||
|
/** @return the type tag code of the entry */
|
||
|
public int getTag() {
|
||
|
return mBuffer.getInt(getHeaderSize());
|
||
|
}
|
||
|
|
||
|
private int getHeaderSize() {
|
||
|
int length = mBuffer.getShort(HEADER_SIZE_OFFSET);
|
||
|
if (length != 0) {
|
||
|
return length;
|
||
|
}
|
||
|
return V1_PAYLOAD_START;
|
||
|
}
|
||
|
/** @return one of Integer, Long, Float, String, null, or Object[] of same. */
|
||
|
public synchronized Object getData() {
|
||
|
try {
|
||
|
int offset = getHeaderSize();
|
||
|
mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET));
|
||
|
if ((offset + TAG_LENGTH) >= mBuffer.limit()) {
|
||
|
// no payload
|
||
|
return null;
|
||
|
}
|
||
|
mBuffer.position(offset + TAG_LENGTH); // Just after the tag.
|
||
|
return decodeObject();
|
||
|
} catch (IllegalArgumentException e) {
|
||
|
Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
|
||
|
mLastWtf = e;
|
||
|
return null;
|
||
|
} catch (BufferUnderflowException e) {
|
||
|
Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e);
|
||
|
mLastWtf = e;
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct a new EventLog object from the current object, copying all log metadata
|
||
|
* but replacing the actual payload with the content provided.
|
||
|
* @hide
|
||
|
*/
|
||
|
public Event withNewData(@Nullable Object object) {
|
||
|
byte[] payload = encodeObject(object);
|
||
|
if (payload.length > 65535 - TAG_LENGTH) {
|
||
|
throw new IllegalArgumentException("Payload too long");
|
||
|
}
|
||
|
int headerLength = getHeaderSize();
|
||
|
byte[] newBytes = new byte[headerLength + TAG_LENGTH + payload.length];
|
||
|
// Copy header (including the 4 bytes of tag integer at the beginning of payload)
|
||
|
System.arraycopy(mBuffer.array(), 0, newBytes, 0, headerLength + TAG_LENGTH);
|
||
|
// Fill in encoded objects
|
||
|
System.arraycopy(payload, 0, newBytes, headerLength + TAG_LENGTH, payload.length);
|
||
|
Event result = new Event(newBytes);
|
||
|
// Patch payload length in header
|
||
|
result.mBuffer.putShort(LENGTH_OFFSET, (short) (payload.length + TAG_LENGTH));
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/** @return the loggable item at the current position in mBuffer. */
|
||
|
private Object decodeObject() {
|
||
|
byte type = mBuffer.get();
|
||
|
switch (type) {
|
||
|
case INT_TYPE:
|
||
|
return mBuffer.getInt();
|
||
|
|
||
|
case LONG_TYPE:
|
||
|
return mBuffer.getLong();
|
||
|
|
||
|
case FLOAT_TYPE:
|
||
|
return mBuffer.getFloat();
|
||
|
|
||
|
case STRING_TYPE:
|
||
|
try {
|
||
|
int length = mBuffer.getInt();
|
||
|
int start = mBuffer.position();
|
||
|
mBuffer.position(start + length);
|
||
|
return new String(mBuffer.array(), start, length, "UTF-8");
|
||
|
} catch (UnsupportedEncodingException e) {
|
||
|
Log.wtf(TAG, "UTF-8 is not supported", e);
|
||
|
mLastWtf = e;
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
case LIST_TYPE:
|
||
|
int length = mBuffer.get();
|
||
|
if (length < 0) length += 256; // treat as signed byte
|
||
|
Object[] array = new Object[length];
|
||
|
for (int i = 0; i < length; ++i) array[i] = decodeObject();
|
||
|
return array;
|
||
|
|
||
|
default:
|
||
|
throw new IllegalArgumentException("Unknown entry type: " + type);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static @NonNull byte[] encodeObject(@Nullable Object object) {
|
||
|
if (object == null) {
|
||
|
return new byte[0];
|
||
|
}
|
||
|
if (object instanceof Integer) {
|
||
|
return ByteBuffer.allocate(1 + 4)
|
||
|
.order(ByteOrder.nativeOrder())
|
||
|
.put(INT_TYPE)
|
||
|
.putInt((Integer) object)
|
||
|
.array();
|
||
|
} else if (object instanceof Long) {
|
||
|
return ByteBuffer.allocate(1 + 8)
|
||
|
.order(ByteOrder.nativeOrder())
|
||
|
.put(LONG_TYPE)
|
||
|
.putLong((Long) object)
|
||
|
.array();
|
||
|
} else if (object instanceof Float) {
|
||
|
return ByteBuffer.allocate(1 + 4)
|
||
|
.order(ByteOrder.nativeOrder())
|
||
|
.put(FLOAT_TYPE)
|
||
|
.putFloat((Float) object)
|
||
|
.array();
|
||
|
} else if (object instanceof String) {
|
||
|
String string = (String) object;
|
||
|
byte[] bytes;
|
||
|
try {
|
||
|
bytes = string.getBytes("UTF-8");
|
||
|
} catch (UnsupportedEncodingException e) {
|
||
|
bytes = new byte[0];
|
||
|
}
|
||
|
return ByteBuffer.allocate(1 + 4 + bytes.length)
|
||
|
.order(ByteOrder.nativeOrder())
|
||
|
.put(STRING_TYPE)
|
||
|
.putInt(bytes.length)
|
||
|
.put(bytes)
|
||
|
.array();
|
||
|
} else if (object instanceof Object[]) {
|
||
|
Object[] objects = (Object[]) object;
|
||
|
if (objects.length > 255) {
|
||
|
throw new IllegalArgumentException("Object array too long");
|
||
|
}
|
||
|
byte[][] bytes = new byte[objects.length][];
|
||
|
int totalLength = 0;
|
||
|
for (int i = 0; i < objects.length; i++) {
|
||
|
bytes[i] = encodeObject(objects[i]);
|
||
|
totalLength += bytes[i].length;
|
||
|
}
|
||
|
ByteBuffer buffer = ByteBuffer.allocate(1 + 1 + totalLength)
|
||
|
.order(ByteOrder.nativeOrder())
|
||
|
.put(LIST_TYPE)
|
||
|
.put((byte) objects.length);
|
||
|
for (int i = 0; i < objects.length; i++) {
|
||
|
buffer.put(bytes[i]);
|
||
|
}
|
||
|
return buffer.array();
|
||
|
} else {
|
||
|
throw new IllegalArgumentException("Unknown object type " + object);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
public static Event fromBytes(byte[] data) {
|
||
|
return new Event(data);
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
public byte[] getBytes() {
|
||
|
byte[] bytes = mBuffer.array();
|
||
|
return Arrays.copyOf(bytes, bytes.length);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retreive the last WTF error generated by this object.
|
||
|
* @hide
|
||
|
*/
|
||
|
//VisibleForTesting
|
||
|
public Exception getLastError() {
|
||
|
return mLastWtf;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clear the error state for this object.
|
||
|
* @hide
|
||
|
*/
|
||
|
//VisibleForTesting
|
||
|
public void clearError() {
|
||
|
mLastWtf = null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@Override
|
||
|
public boolean equals(@Nullable Object o) {
|
||
|
// Not using ByteBuffer.equals since it takes buffer position into account and we
|
||
|
// always use absolute positions here.
|
||
|
if (this == o) return true;
|
||
|
if (o == null || getClass() != o.getClass()) return false;
|
||
|
Event other = (Event) o;
|
||
|
return Arrays.equals(mBuffer.array(), other.mBuffer.array());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
// Not using ByteBuffer.hashCode since it takes buffer position into account and we
|
||
|
// always use absolute positions here.
|
||
|
return Arrays.hashCode(mBuffer.array());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We assume that the native methods deal with any concurrency issues.
|
||
|
|
||
|
/**
|
||
|
* Record an event log message.
|
||
|
* @param tag The event type tag code
|
||
|
* @param value A value to log
|
||
|
* @return The number of bytes written
|
||
|
*/
|
||
|
public static native int writeEvent(int tag, int value);
|
||
|
|
||
|
/**
|
||
|
* Record an event log message.
|
||
|
* @param tag The event type tag code
|
||
|
* @param value A value to log
|
||
|
* @return The number of bytes written
|
||
|
*/
|
||
|
public static native int writeEvent(int tag, long value);
|
||
|
|
||
|
/**
|
||
|
* Record an event log message.
|
||
|
* @param tag The event type tag code
|
||
|
* @param value A value to log
|
||
|
* @return The number of bytes written
|
||
|
*/
|
||
|
public static native int writeEvent(int tag, float value);
|
||
|
|
||
|
/**
|
||
|
* Record an event log message.
|
||
|
* @param tag The event type tag code
|
||
|
* @param str A value to log
|
||
|
* @return The number of bytes written
|
||
|
*/
|
||
|
public static native int writeEvent(int tag, String str);
|
||
|
|
||
|
/**
|
||
|
* Record an event log message.
|
||
|
* @param tag The event type tag code
|
||
|
* @param list A list of values to log
|
||
|
* @return The number of bytes written
|
||
|
*/
|
||
|
public static native int writeEvent(int tag, Object... 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 the log, filtered by type, blocking until logs are about to be overwritten.
|
||
|
* @param tags to search for
|
||
|
* @param timestamp timestamp allow logs before this time to be overwritten.
|
||
|
* @param output container to add events into
|
||
|
* @throws IOException if something goes wrong reading events
|
||
|
* @hide
|
||
|
*/
|
||
|
@SystemApi
|
||
|
public static native void readEventsOnWrapping(int[] tags, long timestamp,
|
||
|
Collection<Event> output)
|
||
|
throws IOException;
|
||
|
|
||
|
/**
|
||
|
* Get the name associated with an event type tag code.
|
||
|
* @param tag code to look up
|
||
|
* @return the name of the tag, or null if no tag has that number
|
||
|
*/
|
||
|
public static String getTagName(int tag) {
|
||
|
readTagsFile();
|
||
|
return sTagNames.get(tag);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the event type tag code associated with an event name.
|
||
|
* @param name of event to look up
|
||
|
* @return the tag code, or -1 if no tag has that name
|
||
|
*/
|
||
|
public static int getTagCode(String name) {
|
||
|
readTagsFile();
|
||
|
Integer code = sTagCodes.get(name);
|
||
|
return code != null ? code : -1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done.
|
||
|
*/
|
||
|
@android.ravenwood.annotation.RavenwoodReplace
|
||
|
private static synchronized void readTagsFile() {
|
||
|
if (sTagCodes != null && sTagNames != null) return;
|
||
|
|
||
|
sTagCodes = new HashMap<String, Integer>();
|
||
|
sTagNames = new HashMap<Integer, String>();
|
||
|
|
||
|
Pattern comment = Pattern.compile(COMMENT_PATTERN);
|
||
|
Pattern tag = Pattern.compile(TAG_PATTERN);
|
||
|
BufferedReader reader = null;
|
||
|
String line;
|
||
|
|
||
|
try {
|
||
|
reader = new BufferedReader(new FileReader(TAGS_FILE), 256);
|
||
|
while ((line = reader.readLine()) != null) {
|
||
|
if (comment.matcher(line).matches()) continue;
|
||
|
|
||
|
Matcher m = tag.matcher(line);
|
||
|
if (!m.matches()) {
|
||
|
Log.wtf(TAG, "Bad entry in " + TAGS_FILE + ": " + line);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
int num = Integer.parseInt(m.group(1));
|
||
|
String name = m.group(2);
|
||
|
registerTagLocked(num, name);
|
||
|
} catch (NumberFormatException e) {
|
||
|
Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e);
|
||
|
}
|
||
|
}
|
||
|
} catch (IOException e) {
|
||
|
Log.wtf(TAG, "Error reading " + TAGS_FILE, e);
|
||
|
// Leave the maps existing but unpopulated
|
||
|
} finally {
|
||
|
try { if (reader != null) reader.close(); } catch (IOException e) {}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void registerTagLocked(int num, String name) {
|
||
|
sTagCodes.put(name, num);
|
||
|
sTagNames.put(num, name);
|
||
|
}
|
||
|
|
||
|
private static synchronized void readTagsFile$ravenwood() {
|
||
|
// TODO: restore parsing logic once we carry into runtime
|
||
|
sTagCodes = new HashMap<String, Integer>();
|
||
|
sTagNames = new HashMap<Integer, String>();
|
||
|
|
||
|
// Hard-code a few common tags
|
||
|
registerTagLocked(524288, "sysui_action");
|
||
|
registerTagLocked(524290, "sysui_count");
|
||
|
registerTagLocked(524291, "sysui_histogram");
|
||
|
}
|
||
|
}
|