/* * 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); } }