blob: 80271abbcea39fe3030c6e3dfff3d508332be735 [file] [log] [blame]
/*
* Copyright (C) 2008 Google Inc.
*
* 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.tools.perflib.heap;
import com.android.annotations.NonNull;
import com.android.tools.perflib.heap.io.HprofBuffer;
import com.google.common.primitives.UnsignedBytes;
import com.google.common.primitives.UnsignedInts;
import java.io.EOFException;
import java.io.IOException;
import gnu.trove.TLongObjectHashMap;
public class HprofParser {
private static final int STRING_IN_UTF8 = 0x01;
private static final int LOAD_CLASS = 0x02;
@SuppressWarnings("UnusedDeclaration")
private static final int UNLOAD_CLASS = 0x03;
private static final int STACK_FRAME = 0x04;
private static final int STACK_TRACE = 0x05;
@SuppressWarnings("UnusedDeclaration")
private static final int ALLOC_SITES = 0x06;
@SuppressWarnings("UnusedDeclaration")
private static final int HEAP_SUMMARY = 0x07;
@SuppressWarnings("UnusedDeclaration")
private static final int START_THREAD = 0x0a;
@SuppressWarnings("UnusedDeclaration")
private static final int END_THREAD = 0x0b;
private static final int HEAP_DUMP = 0x0c;
private static final int HEAP_DUMP_SEGMENT = 0x1c;
@SuppressWarnings("UnusedDeclaration")
private static final int HEAP_DUMP_END = 0x2c;
@SuppressWarnings("UnusedDeclaration")
private static final int CPU_SAMPLES = 0x0d;
@SuppressWarnings("UnusedDeclaration")
private static final int CONTROL_SETTINGS = 0x0e;
private static final int ROOT_UNKNOWN = 0xff;
private static final int ROOT_JNI_GLOBAL = 0x01;
private static final int ROOT_JNI_LOCAL = 0x02;
private static final int ROOT_JAVA_FRAME = 0x03;
private static final int ROOT_NATIVE_STACK = 0x04;
private static final int ROOT_STICKY_CLASS = 0x05;
private static final int ROOT_THREAD_BLOCK = 0x06;
private static final int ROOT_MONITOR_USED = 0x07;
private static final int ROOT_THREAD_OBJECT = 0x08;
private static final int ROOT_CLASS_DUMP = 0x20;
private static final int ROOT_INSTANCE_DUMP = 0x21;
private static final int ROOT_OBJECT_ARRAY_DUMP = 0x22;
private static final int ROOT_PRIMITIVE_ARRAY_DUMP = 0x23;
/**
* Android format addition
*
* Specifies information about which heap certain objects came from. When a sub-tag of this type
* appears in a HPROF_HEAP_DUMP or HPROF_HEAP_DUMP_SEGMENT record, entries that follow it will
* be associated with the specified heap. The HEAP_DUMP_INFO data is reset at the end of the
* HEAP_DUMP[_SEGMENT]. Multiple HEAP_DUMP_INFO entries may appear in a single
* HEAP_DUMP[_SEGMENT].
*
* Format: u1: Tag value (0xFE) u4: heap ID ID: heap name string ID
*/
private static final int ROOT_HEAP_DUMP_INFO = 0xfe;
private static final int ROOT_INTERNED_STRING = 0x89;
private static final int ROOT_FINALIZING = 0x8a;
private static final int ROOT_DEBUGGER = 0x8b;
private static final int ROOT_REFERENCE_CLEANUP = 0x8c;
private static final int ROOT_VM_INTERNAL = 0x8d;
private static final int ROOT_JNI_MONITOR = 0x8e;
private static final int ROOT_UNREACHABLE = 0x90;
private static final int ROOT_PRIMITIVE_ARRAY_NODATA = 0xc3;
@NonNull
private final HprofBuffer mInput;
int mIdSize;
Snapshot mSnapshot;
/*
* These are only needed while parsing so are not kept as part of the
* heap data.
*/
@NonNull
TLongObjectHashMap<String> mStrings = new TLongObjectHashMap<String>();
@NonNull
TLongObjectHashMap<String> mClassNames = new TLongObjectHashMap<String>();
public HprofParser(@NonNull HprofBuffer buffer) {
mInput = buffer;
}
@NonNull
public final Snapshot parse() {
Snapshot snapshot = new Snapshot(mInput);
mSnapshot = snapshot;
try {
try {
readNullTerminatedString(); // Version, ignored for now.
mIdSize = mInput.readInt();
mSnapshot.setIdSize(mIdSize);
mInput.readLong(); // Timestamp, ignored for now.
while (mInput.hasRemaining()) {
int tag = readUnsignedByte();
mInput.readInt(); // Ignored: timestamp
long length = readUnsignedInt();
switch (tag) {
case STRING_IN_UTF8:
// String length is limited by Int.MAX_VALUE anyway.
loadString((int) length - mIdSize);
break;
case LOAD_CLASS:
loadClass();
break;
case STACK_FRAME:
loadStackFrame();
break;
case STACK_TRACE:
loadStackTrace();
break;
case HEAP_DUMP:
loadHeapDump(length);
mSnapshot.setToDefaultHeap();
break;
case HEAP_DUMP_SEGMENT:
loadHeapDump(length);
mSnapshot.setToDefaultHeap();
break;
default:
skipFully(length);
}
}
} catch (EOFException eof) {
// this is fine
}
mSnapshot.resolveClasses();
mSnapshot.resolveReferences();
// TODO: enable this after the dominators computation is also optimized.
// mSnapshot.computeRetainedSizes();
} catch (Exception e) {
e.printStackTrace();
}
mClassNames.clear();
mStrings.clear();
return snapshot;
}
@NonNull
private String readNullTerminatedString() throws IOException {
StringBuilder s = new StringBuilder();
for (byte c = mInput.readByte(); c != 0; c = mInput.readByte()) {
s.append((char) c);
}
return s.toString();
}
private long readId() throws IOException {
// As long as we don't interpret IDs, reading signed values here is fine.
switch (mIdSize) {
case 1:
return mInput.readByte();
case 2:
return mInput.readShort();
case 4:
return mInput.readInt();
case 8:
return mInput.readLong();
}
throw new IllegalArgumentException("ID Length must be 1, 2, 4, or 8");
}
@NonNull
private String readUTF8(int length) throws IOException {
byte[] b = new byte[length];
mInput.read(b);
return new String(b, "utf-8");
}
private int readUnsignedByte() throws IOException {
return UnsignedBytes.toInt(mInput.readByte());
}
private int readUnsignedShort() throws IOException {
return mInput.readShort() & 0xffff;
}
private long readUnsignedInt() throws IOException {
return UnsignedInts.toLong(mInput.readInt());
}
private void loadString(int length) throws IOException {
long id = readId();
String string = readUTF8(length);
mStrings.put(id, string);
}
private void loadClass() throws IOException {
mInput.readInt(); // Ignored: Class serial number.
long id = readId();
mInput.readInt(); // Ignored: Stack trace serial number.
String name = mStrings.get(readId());
mClassNames.put(id, name);
}
private void loadStackFrame() throws IOException {
long id = readId();
String methodName = mStrings.get(readId());
String methodSignature = mStrings.get(readId());
String sourceFile = mStrings.get(readId());
int serial = mInput.readInt();
int lineNumber = mInput.readInt();
StackFrame frame = new StackFrame(id, methodName, methodSignature,
sourceFile, serial, lineNumber);
mSnapshot.addStackFrame(frame);
}
private void loadStackTrace() throws IOException {
int serialNumber = mInput.readInt();
int threadSerialNumber = mInput.readInt();
final int numFrames = mInput.readInt();
StackFrame[] frames = new StackFrame[numFrames];
for (int i = 0; i < numFrames; i++) {
frames[i] = mSnapshot.getStackFrame(readId());
}
StackTrace trace = new StackTrace(serialNumber, threadSerialNumber, frames);
mSnapshot.addStackTrace(trace);
}
private void loadHeapDump(long length) throws IOException {
while (length > 0) {
int tag = readUnsignedByte();
length--;
switch (tag) {
case ROOT_UNKNOWN:
length -= loadBasicObj(RootType.UNKNOWN);
break;
case ROOT_JNI_GLOBAL:
length -= loadBasicObj(RootType.NATIVE_STATIC);
readId(); // ignored
length -= mIdSize;
break;
case ROOT_JNI_LOCAL:
length -= loadJniLocal();
break;
case ROOT_JAVA_FRAME:
length -= loadJavaFrame();
break;
case ROOT_NATIVE_STACK:
length -= loadNativeStack();
break;
case ROOT_STICKY_CLASS:
length -= loadBasicObj(RootType.SYSTEM_CLASS);
break;
case ROOT_THREAD_BLOCK:
length -= loadThreadBlock();
break;
case ROOT_MONITOR_USED:
length -= loadBasicObj(RootType.BUSY_MONITOR);
break;
case ROOT_THREAD_OBJECT:
length -= loadThreadObject();
break;
case ROOT_CLASS_DUMP:
length -= loadClassDump();
break;
case ROOT_INSTANCE_DUMP:
length -= loadInstanceDump();
break;
case ROOT_OBJECT_ARRAY_DUMP:
length -= loadObjectArrayDump();
break;
case ROOT_PRIMITIVE_ARRAY_DUMP:
length -= loadPrimitiveArrayDump();
break;
case ROOT_PRIMITIVE_ARRAY_NODATA:
System.err.println("+--- PRIMITIVE ARRAY NODATA DUMP");
length -= loadPrimitiveArrayDump();
throw new IllegalArgumentException(
"Don't know how to load a nodata array");
case ROOT_HEAP_DUMP_INFO:
int heapId = mInput.readInt();
long heapNameId = readId();
String heapName = mStrings.get(heapNameId);
mSnapshot.setHeapTo(heapId, heapName);
length -= 4 + mIdSize;
break;
case ROOT_INTERNED_STRING:
length -= loadBasicObj(RootType.INTERNED_STRING);
break;
case ROOT_FINALIZING:
length -= loadBasicObj(RootType.FINALIZING);
break;
case ROOT_DEBUGGER:
length -= loadBasicObj(RootType.DEBUGGER);
break;
case ROOT_REFERENCE_CLEANUP:
length -= loadBasicObj(RootType.REFERENCE_CLEANUP);
break;
case ROOT_VM_INTERNAL:
length -= loadBasicObj(RootType.VM_INTERNAL);
break;
case ROOT_JNI_MONITOR:
length -= loadJniMonitor();
break;
case ROOT_UNREACHABLE:
length -= loadBasicObj(RootType.UNREACHABLE);
break;
default:
throw new IllegalArgumentException(
"loadHeapDump loop with unknown tag " + tag
+ " with " + mInput.remaining()
+ " bytes possibly remaining");
}
}
}
private int loadJniLocal() throws IOException {
long id = readId();
int threadSerialNumber = mInput.readInt();
int stackFrameNumber = mInput.readInt();
ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
StackTrace trace = mSnapshot.getStackTraceAtDepth(thread.mStackTrace, stackFrameNumber);
RootObj root = new RootObj(RootType.NATIVE_LOCAL, id, threadSerialNumber, trace);
mSnapshot.addRoot(root);
return mIdSize + 4 + 4;
}
private int loadJavaFrame() throws IOException {
long id = readId();
int threadSerialNumber = mInput.readInt();
int stackFrameNumber = mInput.readInt();
ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
StackTrace trace = mSnapshot.getStackTraceAtDepth(thread.mStackTrace, stackFrameNumber);
RootObj root = new RootObj(RootType.JAVA_LOCAL, id, threadSerialNumber, trace);
mSnapshot.addRoot(root);
return mIdSize + 4 + 4;
}
private int loadNativeStack() throws IOException {
long id = readId();
int threadSerialNumber = mInput.readInt();
ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
StackTrace trace = mSnapshot.getStackTrace(thread.mStackTrace);
RootObj root = new RootObj(RootType.NATIVE_STACK, id, threadSerialNumber, trace);
mSnapshot.addRoot(root);
return mIdSize + 4;
}
private int loadBasicObj(RootType type) throws IOException {
long id = readId();
RootObj root = new RootObj(type, id);
mSnapshot.addRoot(root);
return mIdSize;
}
private int loadThreadBlock() throws IOException {
long id = readId();
int threadSerialNumber = mInput.readInt();
ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
StackTrace stack = mSnapshot.getStackTrace(thread.mStackTrace);
RootObj root = new RootObj(RootType.THREAD_BLOCK, id, threadSerialNumber, stack);
mSnapshot.addRoot(root);
return mIdSize + 4;
}
private int loadThreadObject() throws IOException {
long id = readId();
int threadSerialNumber = mInput.readInt();
int stackSerialNumber = mInput.readInt();
ThreadObj thread = new ThreadObj(id, stackSerialNumber);
mSnapshot.addThread(thread, threadSerialNumber);
return mIdSize + 4 + 4;
}
private int loadClassDump() throws IOException {
final long id = readId();
int stackSerialNumber = mInput.readInt();
StackTrace stack = mSnapshot.getStackTrace(stackSerialNumber);
final long superClassId = readId();
final long classLoaderId = readId();
readId(); // Ignored: Signeres ID.
readId(); // Ignored: Protection domain ID.
readId(); // RESERVED.
readId(); // RESERVED.
int instanceSize = mInput.readInt();
int bytesRead = (7 * mIdSize) + 4 + 4;
// Skip over the constant pool
int numEntries = readUnsignedShort();
bytesRead += 2;
for (int i = 0; i < numEntries; i++) {
readUnsignedShort();
bytesRead += 2 + skipValue();
}
final ClassObj theClass = new ClassObj(id, stack, mClassNames.get(id), mInput.position());
theClass.setSuperClassId(superClassId);
theClass.setClassLoaderId(classLoaderId);
// Skip over static fields
numEntries = readUnsignedShort();
bytesRead += 2;
Field[] staticFields = new Field[numEntries];
for (int i = 0; i < numEntries; i++) {
String name = mStrings.get(readId());
Type type = Type.getType(mInput.readByte());
staticFields[i] = new Field(type, name);
skipFully(mSnapshot.getTypeSize(type));
bytesRead += mIdSize + 1 + mSnapshot.getTypeSize(type);
}
theClass.setStaticFields(staticFields);
// Instance fields
numEntries = readUnsignedShort();
bytesRead += 2;
Field[] fields = new Field[numEntries];
for (int i = 0; i < numEntries; i++) {
String name = mStrings.get(readId());
Type type = Type.getType(readUnsignedByte());
fields[i] = new Field(type, name);
bytesRead += mIdSize + 1;
}
theClass.setFields(fields);
theClass.setInstanceSize(instanceSize);
mSnapshot.addClass(id, theClass);
return bytesRead;
}
private int loadInstanceDump() throws IOException {
long id = readId();
int stackId = mInput.readInt();
StackTrace stack = mSnapshot.getStackTrace(stackId);
long classId = readId();
int remaining = mInput.readInt();
long position = mInput.position();
ClassInstance instance = new ClassInstance(id, stack, position);
instance.setClassId(classId);
mSnapshot.addInstance(id, instance);
skipFully(remaining);
return mIdSize + 4 + mIdSize + 4 + remaining;
}
private int loadObjectArrayDump() throws IOException {
final long id = readId();
int stackId = mInput.readInt();
StackTrace stack = mSnapshot.getStackTrace(stackId);
int numElements = mInput.readInt();
long classId = readId();
ArrayInstance array =
new ArrayInstance(id, stack, Type.OBJECT, numElements, mInput.position());
array.setClassId(classId);
mSnapshot.addInstance(id, array);
int remaining = numElements * mIdSize;
skipFully(remaining);
return mIdSize + 4 + 4 + mIdSize + remaining;
}
private int loadPrimitiveArrayDump() throws IOException {
long id = readId();
int stackId = mInput.readInt();
StackTrace stack = mSnapshot.getStackTrace(stackId);
int numElements = mInput.readInt();
Type type = Type.getType(readUnsignedByte());
int size = mSnapshot.getTypeSize(type);
ArrayInstance array = new ArrayInstance(id, stack, type, numElements, mInput.position());
mSnapshot.addInstance(id, array);
int remaining = numElements * size;
skipFully(remaining);
return mIdSize + 4 + 4 + 1 + remaining;
}
private int loadJniMonitor() throws IOException {
long id = readId();
int threadSerialNumber = mInput.readInt();
int stackDepth = mInput.readInt();
ThreadObj thread = mSnapshot.getThread(threadSerialNumber);
StackTrace trace = mSnapshot.getStackTraceAtDepth(thread.mStackTrace, stackDepth);
RootObj root = new RootObj(RootType.NATIVE_MONITOR, id, threadSerialNumber, trace);
mSnapshot.addRoot(root);
return mIdSize + 4 + 4;
}
private int skipValue() throws IOException {
Type type = Type.getType(readUnsignedByte());
int size = mSnapshot.getTypeSize(type);
skipFully(size);
return size + 1;
}
private void skipFully(long numBytes) throws IOException {
mInput.setPosition(mInput.position() + numBytes);
}
}