blob: 0b2ce69260509f8f1bc313d15f8cfe9861155f6c [file] [log] [blame]
/*
* Copyright (C) 2008 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 com.android.ddmlib.log;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.ddmlib.MultiLineReceiver;
import com.android.ddmlib.log.EventContainer.EventValueType;
import com.android.ddmlib.log.EventValueDescription.ValueType;
import com.android.ddmlib.log.LogReceiver.LogEntry;
import com.android.ddmlib.utils.ArrayHelper;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parser for the "event" log.
*/
public final class EventLogParser {
/** Location of the tag map file on the device */
private final static String EVENT_TAG_MAP_FILE = "/system/etc/event-log-tags"; //$NON-NLS-1$
/**
* Event log entry types. These must match up with the declarations in
* java/android/android/util/EventLog.java.
*/
private final static int EVENT_TYPE_INT = 0;
private final static int EVENT_TYPE_LONG = 1;
private final static int EVENT_TYPE_STRING = 2;
private final static int EVENT_TYPE_LIST = 3;
private final static Pattern PATTERN_SIMPLE_TAG = Pattern.compile(
"^(\\d+)\\s+([A-Za-z0-9_]+)\\s*$"); //$NON-NLS-1$
private final static Pattern PATTERN_TAG_WITH_DESC = Pattern.compile(
"^(\\d+)\\s+([A-Za-z0-9_]+)\\s*(.*)\\s*$"); //$NON-NLS-1$
private final static Pattern PATTERN_DESCRIPTION = Pattern.compile(
"\\(([A-Za-z0-9_\\s]+)\\|(\\d+)(\\|\\d+){0,1}\\)"); //$NON-NLS-1$
private final static Pattern TEXT_LOG_LINE = Pattern.compile(
"(\\d\\d)-(\\d\\d)\\s(\\d\\d):(\\d\\d):(\\d\\d).(\\d{3})\\s+I/([a-zA-Z0-9_]+)\\s*\\(\\s*(\\d+)\\):\\s+(.*)"); //$NON-NLS-1$
private final TreeMap<Integer, String> mTagMap = new TreeMap<Integer, String>();
private final TreeMap<Integer, EventValueDescription[]> mValueDescriptionMap =
new TreeMap<Integer, EventValueDescription[]>();
public EventLogParser() {
}
/**
* Inits the parser for a specific Device.
* <p/>
* This methods reads the event-log-tags located on the device to find out
* what tags are being written to the event log and what their format is.
* @param device The device.
* @return <code>true</code> if success, <code>false</code> if failure or cancellation.
*/
public boolean init(IDevice device) {
// read the event tag map file on the device.
try {
device.executeShellCommand("cat " + EVENT_TAG_MAP_FILE, //$NON-NLS-1$
new MultiLineReceiver() {
@Override
public void processNewLines(String[] lines) {
for (String line : lines) {
processTagLine(line);
}
}
public boolean isCancelled() {
return false;
}
});
} catch (IOException e) {
return false;
}
return true;
}
/**
* Inits the parser with the content of a tag file.
* @param tagFileContent the lines of a tag file.
* @return <code>true</code> if success, <code>false</code> if failure.
*/
public boolean init(String[] tagFileContent) {
for (String line : tagFileContent) {
processTagLine(line);
}
return true;
}
/**
* Inits the parser with a specified event-log-tags file.
* @param filePath
* @return <code>true</code> if success, <code>false</code> if failure.
*/
public boolean init(String filePath) {
try {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String line = null;
do {
line = reader.readLine();
if (line != null) {
processTagLine(line);
}
} while (line != null);
return true;
} catch (IOException e) {
return false;
}
}
/**
* Processes a line from the event-log-tags file.
* @param line the line to process
*/
private void processTagLine(String line) {
// ignore empty lines and comment lines
if (line.length() > 0 && line.charAt(0) != '#') {
Matcher m = PATTERN_TAG_WITH_DESC.matcher(line);
if (m.matches()) {
try {
int value = Integer.parseInt(m.group(1));
String name = m.group(2);
if (name != null && mTagMap.get(value) == null) {
mTagMap.put(value, name);
}
// special case for the GC tag. We ignore what is in the file,
// and take what the custom GcEventContainer class tells us.
// This is due to the event encoding several values on 2 longs.
// @see GcEventContainer
if (value == GcEventContainer.GC_EVENT_TAG) {
mValueDescriptionMap.put(value,
GcEventContainer.getValueDescriptions());
} else {
String description = m.group(3);
if (description != null && description.length() > 0) {
EventValueDescription[] desc =
processDescription(description);
if (desc != null) {
mValueDescriptionMap.put(value, desc);
}
}
}
} catch (NumberFormatException e) {
// failed to convert the number into a string. just ignore it.
}
} else {
m = PATTERN_SIMPLE_TAG.matcher(line);
if (m.matches()) {
int value = Integer.parseInt(m.group(1));
String name = m.group(2);
if (name != null && mTagMap.get(value) == null) {
mTagMap.put(value, name);
}
}
}
}
}
private EventValueDescription[] processDescription(String description) {
String[] descriptions = description.split("\\s*,\\s*"); //$NON-NLS-1$
ArrayList<EventValueDescription> list = new ArrayList<EventValueDescription>();
for (String desc : descriptions) {
Matcher m = PATTERN_DESCRIPTION.matcher(desc);
if (m.matches()) {
try {
String name = m.group(1);
String typeString = m.group(2);
int typeValue = Integer.parseInt(typeString);
EventValueType eventValueType = EventValueType.getEventValueType(typeValue);
if (eventValueType == null) {
// just ignore this description if the value is not recognized.
// TODO: log the error.
}
typeString = m.group(3);
if (typeString != null && typeString.length() > 0) {
//skip the |
typeString = typeString.substring(1);
typeValue = Integer.parseInt(typeString);
ValueType valueType = ValueType.getValueType(typeValue);
list.add(new EventValueDescription(name, eventValueType, valueType));
} else {
list.add(new EventValueDescription(name, eventValueType));
}
} catch (NumberFormatException nfe) {
// just ignore this description if one number is malformed.
// TODO: log the error.
} catch (InvalidValueTypeException e) {
// just ignore this description if data type and data unit don't match
// TODO: log the error.
}
} else {
Log.e("EventLogParser", //$NON-NLS-1$
String.format("Can't parse %1$s", description)); //$NON-NLS-1$
}
}
if (list.size() == 0) {
return null;
}
return list.toArray(new EventValueDescription[list.size()]);
}
public EventContainer parse(LogEntry entry) {
if (entry.len < 4) {
return null;
}
int inOffset = 0;
int tagValue = ArrayHelper.swap32bitFromArray(entry.data, inOffset);
inOffset += 4;
String tag = mTagMap.get(tagValue);
if (tag == null) {
Log.e("EventLogParser", String.format("unknown tag number: %1$d", tagValue));
}
ArrayList<Object> list = new ArrayList<Object>();
if (parseBinaryEvent(entry.data, inOffset, list) == -1) {
return null;
}
Object data;
if (list.size() == 1) {
data = list.get(0);
} else{
data = list.toArray();
}
EventContainer event = null;
if (tagValue == GcEventContainer.GC_EVENT_TAG) {
event = new GcEventContainer(entry, tagValue, data);
} else {
event = new EventContainer(entry, tagValue, data);
}
return event;
}
public EventContainer parse(String textLogLine) {
// line will look like
// 04-29 23:16:16.691 I/dvm_gc_info( 427): <data>
// where <data> is either
// [value1,value2...]
// or
// value
if (textLogLine.length() == 0) {
return null;
}
// parse the header first
Matcher m = TEXT_LOG_LINE.matcher(textLogLine);
if (m.matches()) {
try {
int month = Integer.parseInt(m.group(1));
int day = Integer.parseInt(m.group(2));
int hours = Integer.parseInt(m.group(3));
int minutes = Integer.parseInt(m.group(4));
int seconds = Integer.parseInt(m.group(5));
int milliseconds = Integer.parseInt(m.group(6));
// convert into seconds since epoch and nano-seconds.
Calendar cal = Calendar.getInstance();
cal.set(cal.get(Calendar.YEAR), month-1, day, hours, minutes, seconds);
int sec = (int)Math.floor(cal.getTimeInMillis()/1000);
int nsec = milliseconds * 1000000;
String tag = m.group(7);
// get the numerical tag value
int tagValue = -1;
Set<Entry<Integer, String>> tagSet = mTagMap.entrySet();
for (Entry<Integer, String> entry : tagSet) {
if (tag.equals(entry.getValue())) {
tagValue = entry.getKey();
break;
}
}
if (tagValue == -1) {
return null;
}
int pid = Integer.parseInt(m.group(8));
Object data = parseTextData(m.group(9), tagValue);
if (data == null) {
return null;
}
// now we can allocate and return the EventContainer
EventContainer event = null;
if (tagValue == GcEventContainer.GC_EVENT_TAG) {
event = new GcEventContainer(tagValue, pid, -1 /* tid */, sec, nsec, data);
} else {
event = new EventContainer(tagValue, pid, -1 /* tid */, sec, nsec, data);
}
return event;
} catch (NumberFormatException e) {
return null;
}
}
return null;
}
public Map<Integer, String> getTagMap() {
return mTagMap;
}
public Map<Integer, EventValueDescription[]> getEventInfoMap() {
return mValueDescriptionMap;
}
/**
* Recursively convert binary log data to printable form.
*
* This needs to be recursive because you can have lists of lists.
*
* If we run out of room, we stop processing immediately. It's important
* for us to check for space on every output element to avoid producing
* garbled output.
*
* Returns the amount read on success, -1 on failure.
*/
private static int parseBinaryEvent(byte[] eventData, int dataOffset, ArrayList<Object> list) {
if (eventData.length - dataOffset < 1)
return -1;
int offset = dataOffset;
int type = eventData[offset++];
//fprintf(stderr, "--- type=%d (rem len=%d)\n", type, eventDataLen);
switch (type) {
case EVENT_TYPE_INT: { /* 32-bit signed int */
int ival;
if (eventData.length - offset < 4)
return -1;
ival = ArrayHelper.swap32bitFromArray(eventData, offset);
offset += 4;
list.add(new Integer(ival));
}
break;
case EVENT_TYPE_LONG: { /* 64-bit signed long */
long lval;
if (eventData.length - offset < 8)
return -1;
lval = ArrayHelper.swap64bitFromArray(eventData, offset);
offset += 8;
list.add(new Long(lval));
}
break;
case EVENT_TYPE_STRING: { /* UTF-8 chars, not NULL-terminated */
int strLen;
if (eventData.length - offset < 4)
return -1;
strLen = ArrayHelper.swap32bitFromArray(eventData, offset);
offset += 4;
if (eventData.length - offset < strLen)
return -1;
// get the string
try {
String str = new String(eventData, offset, strLen, "UTF-8"); //$NON-NLS-1$
list.add(str);
} catch (UnsupportedEncodingException e) {
}
offset += strLen;
break;
}
case EVENT_TYPE_LIST: { /* N items, all different types */
if (eventData.length - offset < 1)
return -1;
int count = eventData[offset++];
// make a new temp list
ArrayList<Object> subList = new ArrayList<Object>();
for (int i = 0; i < count; i++) {
int result = parseBinaryEvent(eventData, offset, subList);
if (result == -1) {
return result;
}
offset += result;
}
list.add(subList.toArray());
}
break;
default:
Log.e("EventLogParser", //$NON-NLS-1$
String.format("Unknown binary event type %1$d", type)); //$NON-NLS-1$
return -1;
}
return offset - dataOffset;
}
private Object parseTextData(String data, int tagValue) {
// first, get the description of what we're supposed to parse
EventValueDescription[] desc = mValueDescriptionMap.get(tagValue);
if (desc == null) {
// TODO parse and create string values.
return null;
}
if (desc.length == 1) {
return getObjectFromString(data, desc[0].getEventValueType());
} else if (data.startsWith("[") && data.endsWith("]")) {
data = data.substring(1, data.length() - 1);
// get each individual values as String
String[] values = data.split(",");
if (tagValue == GcEventContainer.GC_EVENT_TAG) {
// special case for the GC event!
Object[] objects = new Object[2];
objects[0] = getObjectFromString(values[0], EventValueType.LONG);
objects[1] = getObjectFromString(values[1], EventValueType.LONG);
return objects;
} else {
// must be the same number as the number of descriptors.
if (values.length != desc.length) {
return null;
}
Object[] objects = new Object[values.length];
for (int i = 0 ; i < desc.length ; i++) {
Object obj = getObjectFromString(values[i], desc[i].getEventValueType());
if (obj == null) {
return null;
}
objects[i] = obj;
}
return objects;
}
}
return null;
}
private Object getObjectFromString(String value, EventValueType type) {
try {
switch (type) {
case INT:
return Integer.valueOf(value);
case LONG:
return Long.valueOf(value);
case STRING:
return value;
}
} catch (NumberFormatException e) {
// do nothing, we'll return null.
}
return null;
}
/**
* Recreates the event-log-tags at the specified file path.
* @param filePath the file path to write the file.
* @throws IOException
*/
public void saveTags(String filePath) throws IOException {
File destFile = new File(filePath);
destFile.createNewFile();
FileOutputStream fos = null;
try {
fos = new FileOutputStream(destFile);
for (Integer key : mTagMap.keySet()) {
// get the tag name
String tagName = mTagMap.get(key);
// get the value descriptions
EventValueDescription[] descriptors = mValueDescriptionMap.get(key);
String line = null;
if (descriptors != null) {
StringBuilder sb = new StringBuilder();
sb.append(String.format("%1$d %2$s", key, tagName)); //$NON-NLS-1$
boolean first = true;
for (EventValueDescription evd : descriptors) {
if (first) {
sb.append(" ("); //$NON-NLS-1$
first = false;
} else {
sb.append(",("); //$NON-NLS-1$
}
sb.append(evd.getName());
sb.append("|"); //$NON-NLS-1$
sb.append(evd.getEventValueType().getValue());
sb.append("|"); //$NON-NLS-1$
sb.append(evd.getValueType().getValue());
sb.append("|)"); //$NON-NLS-1$
}
sb.append("\n"); //$NON-NLS-1$
line = sb.toString();
} else {
line = String.format("%1$d %2$s\n", key, tagName); //$NON-NLS-1$
}
byte[] buffer = line.getBytes();
fos.write(buffer);
}
} finally {
if (fos != null) {
fos.close();
}
}
}
}