org.eclipse.mat.hprof.Pass1Parser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of haha Show documentation
Show all versions of haha Show documentation
Java library to automate the analysis of Android heap dumps.
/**
* ****************************************************************************
* Copyright (c) 2008 SAP AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
* *****************************************************************************
*/
package org.eclipse.mat.hprof;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.HashMapLongObject;
import org.eclipse.mat.parser.io.PositionInputStream;
import org.eclipse.mat.parser.model.ClassImpl;
import org.eclipse.mat.snapshot.model.Field;
import org.eclipse.mat.snapshot.model.FieldDescriptor;
import org.eclipse.mat.snapshot.model.GCRootInfo;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IPrimitiveArray;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.IProgressListener.Severity;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.SimpleMonitor;
public class Pass1Parser extends AbstractParser {
private static final Pattern PATTERN_OBJ_ARRAY = Pattern.compile("^(\\[+)L(.*);$"); //$NON-NLS-1$
private static final Pattern PATTERN_PRIMITIVE_ARRAY = Pattern.compile("^(\\[+)(.)$");
//$NON-NLS-1$
private HashMapLongObject class2name = new HashMapLongObject();
private HashMapLongObject thread2id = new HashMapLongObject();
private HashMapLongObject id2frame = new HashMapLongObject();
private HashMapLongObject serNum2stackTrace = new HashMapLongObject();
private HashMapLongObject classSerNum2id = new HashMapLongObject();
private HashMapLongObject> thread2locals =
new HashMapLongObject>();
private IHprofParserHandler handler;
private SimpleMonitor.Listener monitor;
public Pass1Parser(IHprofParserHandler handler, SimpleMonitor.Listener monitor) {
this.handler = handler;
this.monitor = monitor;
}
public void read(File file) throws SnapshotException, IOException {
in = new PositionInputStream(new BufferedInputStream(new FileInputStream(file)));
final int dumpNrToRead = determineDumpNumber();
int currentDumpNr = 0;
try {
// header & version
version = readVersion(in);
handler.addProperty(IHprofParserHandler.VERSION, version.toString());
// identifierSize (32 or 64 bit)
idSize = in.readInt();
if (idSize != 4 && idSize != 8) {
throw new SnapshotException(Messages.Pass1Parser_Error_SupportedDumps);
}
handler.addProperty(IHprofParserHandler.IDENTIFIER_SIZE, String.valueOf(idSize));
// creation date
long date = in.readLong();
handler.addProperty(IHprofParserHandler.CREATION_DATE, String.valueOf(date));
long fileSize = file.length();
long curPos = in.position();
while (curPos < fileSize) {
if (monitor.isProbablyCanceled()) throw new IProgressListener.OperationCanceledException();
monitor.totalWorkDone(curPos / 1000);
int record = in.readUnsignedByte();
in.skipBytes(4); // time stamp
long length = readUnsignedInt();
if (length < 0) {
throw new SnapshotException(
MessageUtil.format(Messages.Pass1Parser_Error_IllegalRecordLength, in.position()));
}
if (curPos + length - 9 > fileSize) {
monitor.sendUserMessage(Severity.WARNING,
MessageUtil.format(Messages.Pass1Parser_Error_invalidHPROFFile, length,
fileSize - curPos - 9), null);
}
switch (record) {
case Constants.Record.STRING_IN_UTF8:
readString(length);
break;
case Constants.Record.LOAD_CLASS:
readLoadClass();
break;
case Constants.Record.STACK_FRAME:
readStackFrame(length);
break;
case Constants.Record.STACK_TRACE:
readStackTrace(length);
break;
case Constants.Record.HEAP_DUMP:
case Constants.Record.HEAP_DUMP_SEGMENT:
if (dumpNrToRead == currentDumpNr) {
readDumpSegments(length);
} else {
in.skipBytes(length);
}
if (record == Constants.Record.HEAP_DUMP) currentDumpNr++;
break;
case Constants.Record.HEAP_DUMP_END:
currentDumpNr++;
default:
in.skipBytes(length);
break;
}
curPos = in.position();
}
} finally {
try {
in.close();
} catch (IOException ignore) {
}
}
if (currentDumpNr <= dumpNrToRead) {
throw new SnapshotException(
MessageUtil.format(Messages.Pass1Parser_Error_NoHeapDumpIndexFound, currentDumpNr,
file.getName(), dumpNrToRead));
}
if (currentDumpNr > 1) {
monitor.sendUserMessage(IProgressListener.Severity.INFO,
MessageUtil.format(Messages.Pass1Parser_Info_UsingDumpIndex, currentDumpNr,
file.getName(), dumpNrToRead), null);
}
if (serNum2stackTrace.size() > 0) dumpThreads();
}
private void readString(long length) throws IOException {
long id = readID();
byte[] chars = new byte[(int) (length - idSize)];
in.readFully(chars);
handler.getConstantPool().put(id, new String(chars));
}
private void readLoadClass() throws IOException {
long classSerNum = readUnsignedInt(); // used in stacks frames
long classID = readID();
in.skipBytes(4);
long nameID = readID();
String className = getStringConstant(nameID).replace('/', '.');
class2name.put(classID, className);
classSerNum2id.put(classSerNum, classID);
}
private void readStackFrame(long length) throws IOException {
long frameId = readID();
long methodName = readID();
long methodSig = readID();
long srcFile = readID();
long classSerNum = readUnsignedInt();
int lineNr = in.readInt(); // can be negative
StackFrame frame =
new StackFrame(frameId, lineNr, getStringConstant(methodName), getStringConstant(methodSig),
getStringConstant(srcFile), classSerNum);
id2frame.put(frameId, frame);
}
private void readStackTrace(long length) throws IOException {
long stackTraceNr = readUnsignedInt();
long threadNr = readUnsignedInt();
long frameCount = readUnsignedInt();
long[] frameIds = new long[(int) frameCount];
for (int i = 0; i < frameCount; i++) {
frameIds[i] = readID();
}
StackTrace stackTrace = new StackTrace(stackTraceNr, threadNr, frameIds);
serNum2stackTrace.put(stackTraceNr, stackTrace);
}
private void readDumpSegments(long length) throws IOException, SnapshotException {
long segmentStartPos = in.position();
long segmentsEndPos = segmentStartPos + length;
while (segmentStartPos < segmentsEndPos) {
long workDone = segmentStartPos / 1000;
if (this.monitor.getWorkDone() < workDone) {
if (this.monitor.isProbablyCanceled()) {
throw new IProgressListener.OperationCanceledException();
}
this.monitor.totalWorkDone(workDone);
}
int segmentType = in.readUnsignedByte();
switch (segmentType) {
case Constants.DumpSegment.ROOT_UNKNOWN:
readGC(GCRootInfo.Type.UNKNOWN, 0);
break;
case Constants.DumpSegment.ROOT_THREAD_OBJECT:
readGCThreadObject(GCRootInfo.Type.THREAD_OBJ);
break;
case Constants.DumpSegment.ROOT_JNI_GLOBAL:
readGC(GCRootInfo.Type.NATIVE_STACK, idSize);
break;
case Constants.DumpSegment.ROOT_JNI_LOCAL:
readGCWithThreadContext(GCRootInfo.Type.NATIVE_LOCAL, true);
break;
case Constants.DumpSegment.ROOT_JAVA_FRAME:
readGCWithThreadContext(GCRootInfo.Type.JAVA_LOCAL, true);
break;
case Constants.DumpSegment.ROOT_NATIVE_STACK:
readGCWithThreadContext(GCRootInfo.Type.NATIVE_STACK, false);
break;
case Constants.DumpSegment.ROOT_STICKY_CLASS:
readGC(GCRootInfo.Type.SYSTEM_CLASS, 0);
break;
case Constants.DumpSegment.ROOT_THREAD_BLOCK:
readGC(GCRootInfo.Type.THREAD_BLOCK, 4);
break;
case Constants.DumpSegment.ROOT_MONITOR_USED:
readGC(GCRootInfo.Type.BUSY_MONITOR, 0);
break;
case Constants.DumpSegment.CLASS_DUMP:
readClassDump(segmentStartPos);
break;
case Constants.DumpSegment.INSTANCE_DUMP:
readInstanceDump(segmentStartPos);
break;
case Constants.DumpSegment.OBJECT_ARRAY_DUMP:
readObjectArrayDump(segmentStartPos);
break;
case Constants.DumpSegment.PRIMITIVE_ARRAY_DUMP:
readPrimitiveArrayDump(segmentStartPos);
break;
/* these were added for Android in 1.0.3 */
case Constants.DumpSegment.ANDROID_HEAP_DUMP_INFO:
// no 1.0.2 equivalent for this
in.skipBytes(idSize + 4);
break;
case Constants.DumpSegment.ANDROID_ROOT_INTERNED_STRING:
readGC(GCRootInfo.Type.UNKNOWN, 0);
break;
case Constants.DumpSegment.ANDROID_ROOT_FINALIZING:
readGC(GCRootInfo.Type.UNKNOWN, 0);
break;
case Constants.DumpSegment.ANDROID_ROOT_DEBUGGER:
readGC(GCRootInfo.Type.UNKNOWN, 0);
break;
case Constants.DumpSegment.ANDROID_ROOT_REFERENCE_CLEANUP:
readGC(GCRootInfo.Type.UNKNOWN, 0);
break;
case Constants.DumpSegment.ANDROID_ROOT_VM_INTERNAL:
readGC(GCRootInfo.Type.UNKNOWN, 0);
break;
case Constants.DumpSegment.ANDROID_ROOT_JNI_MONITOR:
/* keep the ident, drop the next 8 bytes */
readGC(GCRootInfo.Type.UNKNOWN, 8);
break;
case Constants.DumpSegment.ANDROID_UNREACHABLE:
readGC(GCRootInfo.Type.UNKNOWN, 0);
break;
case Constants.DumpSegment.ANDROID_PRIMITIVE_ARRAY_NODATA_DUMP:
readPrimitiveArrayNoDataDump(segmentStartPos);
break;
default:
throw new SnapshotException(
MessageUtil.format(Messages.Pass1Parser_Error_InvalidHeapDumpFile, segmentType,
segmentStartPos));
}
segmentStartPos = in.position();
}
}
private void readGCThreadObject(int gcType) throws IOException {
long id = readID();
int threadSerialNo = in.readInt();
thread2id.put(threadSerialNo, id);
handler.addGCRoot(id, 0, gcType);
in.skipBytes(4);
}
private void readGC(int gcType, int skip) throws IOException {
long id = readID();
handler.addGCRoot(id, 0, gcType);
if (skip > 0) in.skipBytes(skip);
}
private void readGCWithThreadContext(int gcType, boolean hasLineInfo) throws IOException {
long id = readID();
int threadSerialNo = in.readInt();
Long tid = thread2id.get(threadSerialNo);
if (tid != null) {
handler.addGCRoot(id, tid, gcType);
} else {
handler.addGCRoot(id, 0, gcType);
}
if (hasLineInfo) {
int lineNumber = in.readInt();
List locals = thread2locals.get(threadSerialNo);
if (locals == null) {
locals = new ArrayList();
thread2locals.put(threadSerialNo, locals);
}
locals.add(new JavaLocal(id, lineNumber, gcType));
}
}
private void readClassDump(long segmentStartPos) throws IOException {
long address = readID();
in.skipBytes(4); // stack trace serial number
long superClassObjectId = readID();
long classLoaderObjectId = readID();
// skip signers, protection domain, reserved ids (2), instance size
in.skipBytes(this.idSize * 4 + 4);
// constant pool: u2 ( u2 u1 value )*
int constantPoolSize = in.readUnsignedShort();
for (int ii = 0; ii < constantPoolSize; ii++) {
in.skipBytes(2); // index
skipValue(); // value
}
// static fields: u2 num ( name ID, u1 type, value)
int numStaticFields = in.readUnsignedShort();
Field[] statics = new Field[numStaticFields];
for (int ii = 0; ii < numStaticFields; ii++) {
long nameId = readID();
String name = getStringConstant(nameId);
byte type = in.readByte();
Object value = readValue(null, type);
statics[ii] = new Field(name, type, value);
}
// instance fields: u2 num ( name ID, u1 type )
int numInstanceFields = in.readUnsignedShort();
FieldDescriptor[] fields = new FieldDescriptor[numInstanceFields];
for (int ii = 0; ii < numInstanceFields; ii++) {
long nameId = readID();
String name = getStringConstant(nameId);
byte type = in.readByte();
fields[ii] = new FieldDescriptor(name, type);
}
// get name
String className = class2name.get(address);
if (className == null) className = "unknown-name@0x" + Long.toHexString(address); //$NON-NLS-1$
if (className.charAt(0) == '[') // quick check if array at hand
{
// fix object class names
Matcher matcher = PATTERN_OBJ_ARRAY.matcher(className);
if (matcher.matches()) {
int l = matcher.group(1).length();
className = matcher.group(2);
for (int ii = 0; ii < l; ii++)
className += "[]"; //$NON-NLS-1$
}
// primitive arrays
matcher = PATTERN_PRIMITIVE_ARRAY.matcher(className);
if (matcher.matches()) {
int count = matcher.group(1).length() - 1;
className = "unknown[]"; //$NON-NLS-1$
char signature = matcher.group(2).charAt(0);
for (int ii = 0; ii < IPrimitiveArray.SIGNATURES.length; ii++) {
if (IPrimitiveArray.SIGNATURES[ii] == (byte) signature) {
className = IPrimitiveArray.TYPE[ii];
break;
}
}
for (int ii = 0; ii < count; ii++)
className += "[]"; //$NON-NLS-1$
}
}
ClassImpl clazz =
new ClassImpl(address, className, superClassObjectId, classLoaderObjectId, statics, fields);
handler.addClass(clazz, segmentStartPos);
}
private void readInstanceDump(long segmentStartPos) throws IOException {
long address = readID();
handler.reportInstance(address, segmentStartPos);
in.skipBytes(idSize + 4);
int payload = in.readInt();
in.skipBytes(payload);
}
private void readObjectArrayDump(long segmentStartPos) throws IOException {
long address = readID();
handler.reportInstance(address, segmentStartPos);
in.skipBytes(4);
int size = in.readInt();
long arrayClassObjectID = readID();
// check if class needs to be created
IClass arrayType = handler.lookupClass(arrayClassObjectID);
if (arrayType == null) handler.reportRequiredObjectArray(arrayClassObjectID);
in.skipBytes(size * idSize);
}
private void readPrimitiveArrayDump(long segmentStartPos) throws SnapshotException, IOException {
long address = readID();
handler.reportInstance(address, segmentStartPos);
in.skipBytes(4);
int size = in.readInt();
byte elementType = in.readByte();
if ((elementType < IPrimitiveArray.Type.BOOLEAN) || (elementType > IPrimitiveArray.Type.LONG)) {
throw new SnapshotException(Messages.Pass1Parser_Error_IllegalType);
}
// check if class needs to be created
String name = IPrimitiveArray.TYPE[elementType];
IClass clazz = handler.lookupClassByName(name, true);
if (clazz == null) handler.reportRequiredPrimitiveArray(elementType);
int elementSize = IPrimitiveArray.ELEMENT_SIZE[elementType];
in.skipBytes(elementSize * size);
}
/* Added for Android in 1.0.3 */
private void readPrimitiveArrayNoDataDump(long segmentStartPos)
throws SnapshotException, IOException {
long address = readID();
handler.reportInstance(address, segmentStartPos);
in.skipBytes(4);
int size = in.readInt();
byte elementType = in.readByte();
if ((elementType < IPrimitiveArray.Type.BOOLEAN) || (elementType > IPrimitiveArray.Type.LONG)) {
throw new SnapshotException(Messages.Pass1Parser_Error_IllegalType);
}
// check if class needs to be created
String name = IPrimitiveArray.TYPE[elementType];
IClass clazz = handler.lookupClassByName(name, true);
if (clazz == null) {
handler.reportRequiredPrimitiveArray(elementType);
}
}
private String getStringConstant(long address) {
if (address == 0L) return ""; //$NON-NLS-1$
String result = handler.getConstantPool().get(address);
return result == null ? Messages.Pass1Parser_Error_UnresolvedName + Long.toHexString(address)
: result;
}
private void dumpThreads() {
// noticed that one stack trace with empty stack is always reported,
// even if the dump has no call stacks info
if (serNum2stackTrace == null || serNum2stackTrace.size() <= 1) return;
PrintWriter out = null;
String outputName = handler.getSnapshotInfo().getPrefix() + "threads"; //$NON-NLS-1$
try {
out = new PrintWriter(new FileWriter(outputName));
Iterator it = serNum2stackTrace.values();
while (it.hasNext()) {
StackTrace stack = it.next();
Long tid = thread2id.get(stack.threadSerialNr);
if (tid == null) continue;
String threadId =
tid == null ? "" : "0x" + Long.toHexString(tid); //$NON-NLS-1$ //$NON-NLS-2$
out.println("Thread " + threadId); //$NON-NLS-1$
out.println(stack);
out.println(" locals:"); //$NON-NLS-1$
List locals = thread2locals.get(stack.threadSerialNr);
if (locals != null) {
for (JavaLocal javaLocal : locals) {
out.println(" objecId=0x"
+ Long.toHexString(javaLocal.objectId)
+ ", line="
+ javaLocal.lineNumber); //$NON-NLS-1$ //$NON-NLS-2$
}
}
out.println();
}
out.flush();
this.monitor.sendUserMessage(Severity.INFO,
MessageUtil.format(Messages.Pass1Parser_Info_WroteThreadsTo, outputName), null);
} catch (IOException e) {
this.monitor.sendUserMessage(Severity.WARNING,
MessageUtil.format(Messages.Pass1Parser_Error_WritingThreadsInformation), e);
} finally {
if (out != null) {
try {
out.close();
} catch (Exception ignore) {
// $JL-EXC$
}
}
}
}
private class StackFrame {
final long frameId;
final String method;
final String methodSignature;
final String sourceFile;
final long classSerNum;
/*
* > 0 line number
* 0 no line info
* -1 unknown location
* -2 compiled method
* -3 native method
*/
final int lineNr;
public StackFrame(long frameId, int lineNr, String method, String methodSignature,
String sourceFile, long classSerNum) {
this.frameId = frameId;
this.lineNr = lineNr;
this.method = method;
this.methodSignature = methodSignature;
this.sourceFile = sourceFile;
this.classSerNum = classSerNum;
}
@Override public String toString() {
String className = null;
Long classId = classSerNum2id.get(classSerNum);
if (classId == null) {
className = ""; //$NON-NLS-1$
} else {
className = class2name.get(classId);
}
String sourceLocation = ""; //$NON-NLS-1$
if (lineNr > 0) {
sourceLocation = "("
+ sourceFile
+ ":"
+ String.valueOf(lineNr)
+ ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
} else if (lineNr == 0 || lineNr == -1) {
sourceLocation = "(Unknown Source)"; //$NON-NLS-1$
} else if (lineNr == -2) {
sourceLocation = "(Compiled method)"; //$NON-NLS-1$
} else if (lineNr == -3) {
sourceLocation = "(Native Method)"; //$NON-NLS-1$
}
return " at "
+ className
+ "."
+ method
+ methodSignature
+ " "
+ sourceLocation; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
private class StackTrace {
final long threadSerialNr;
final long[] frameIds;
public StackTrace(long serialNr, long threadSerialNr, long[] frameIds) {
this.threadSerialNr = threadSerialNr;
this.frameIds = frameIds;
}
@Override public String toString() {
StringBuilder b = new StringBuilder();
for (long frameId : frameIds) {
StackFrame frame = id2frame.get(frameId);
if (frame != null) {
b.append(frame).append("\r\n"); //$NON-NLS-1$
}
}
return b.toString();
}
}
private static class JavaLocal {
final long objectId;
final int lineNumber;
final int type;
public JavaLocal(long objectId, int lineNumber, int type) {
this.objectId = objectId;
this.lineNumber = lineNumber;
this.type = type;
}
public int getType() {
return type;
}
}
}