diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
commit | f6c387128427e121477c1b32ad35cdcaa5101ba3 (patch) | |
tree | 2aa25fa8c8c3a9caeecf98fd8ac4cd9b12717997 /hit | |
parent | f72d5de56a522ac3be03873bdde26f23a5eeeb3c (diff) | |
download | android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.tar.gz android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.tar.bz2 android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.zip |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'hit')
-rw-r--r-- | hit/samples/android.hprof | bin | 0 -> 8569517 bytes | |||
-rw-r--r-- | hit/src/com/android/hit/ArrayInstance.java | 188 | ||||
-rw-r--r-- | hit/src/com/android/hit/ClassInstance.java | 208 | ||||
-rw-r--r-- | hit/src/com/android/hit/ClassObj.java | 241 | ||||
-rw-r--r-- | hit/src/com/android/hit/Heap.java | 185 | ||||
-rw-r--r-- | hit/src/com/android/hit/HprofParser.java | 611 | ||||
-rw-r--r-- | hit/src/com/android/hit/Instance.java | 117 | ||||
-rw-r--r-- | hit/src/com/android/hit/Main.java | 97 | ||||
-rw-r--r-- | hit/src/com/android/hit/Queries.java | 239 | ||||
-rw-r--r-- | hit/src/com/android/hit/RootObj.java | 111 | ||||
-rw-r--r-- | hit/src/com/android/hit/RootType.java | 53 | ||||
-rw-r--r-- | hit/src/com/android/hit/StackFrame.java | 60 | ||||
-rw-r--r-- | hit/src/com/android/hit/StackTrace.java | 64 | ||||
-rw-r--r-- | hit/src/com/android/hit/State.java | 179 | ||||
-rw-r--r-- | hit/src/com/android/hit/ThreadObj.java | 27 | ||||
-rw-r--r-- | hit/src/com/android/hit/Types.java | 89 | ||||
-rwxr-xr-x | hit/test/testparser | 8 |
17 files changed, 2477 insertions, 0 deletions
diff --git a/hit/samples/android.hprof b/hit/samples/android.hprof Binary files differnew file mode 100644 index 000000000..bb8d2e0f2 --- /dev/null +++ b/hit/samples/android.hprof diff --git a/hit/src/com/android/hit/ArrayInstance.java b/hit/src/com/android/hit/ArrayInstance.java new file mode 100644 index 000000000..cdb56167f --- /dev/null +++ b/hit/src/com/android/hit/ArrayInstance.java @@ -0,0 +1,188 @@ +/* + * 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.hit; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.util.Set; + +public class ArrayInstance extends Instance { + private int mType; + private int mNumEntries; + private byte[] mData; + + public ArrayInstance(long id, StackTrace stack, int type, int numEntries, + byte[] data) { + mId = id; + mStack = stack; + mType = type; + mNumEntries = numEntries; + mData = data; + } + + public final void resolveReferences(State state) { + if (mType != Types.OBJECT) { + return; + } + + /* + * mData holds a stream of object instance ids + * Spin through them all and list ourselves as a reference holder. + */ + int idSize = Types.getTypeSize(mType); + final int N = mNumEntries; + + ByteArrayInputStream bais = new ByteArrayInputStream(mData); + DataInputStream dis = new DataInputStream(bais); + + for (int i = 0; i < N; i++) { + long id; + + try { + if (idSize == 4) { + id = dis.readInt(); + } else { + id = dis.readLong(); + } + + Instance instance = state.findReference(id); + + if (instance != null) { + instance.addParent(this); + } + } catch (java.io.IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public final int getSize() { + return mData.length; + } + + @Override + public final void visit(Set<Instance> resultSet, Filter filter) { + // If we're in the set then we and our children have been visited + if (resultSet.contains(this)) { + return; + } + + if (null != filter) { + if (filter.accept(this)) { + resultSet.add(this); + } + } else { + resultSet.add(this); + } + + if (mType != Types.OBJECT) { + return; + } + + /* + * mData holds a stream of object instance ids + * Spin through them all and visit them + */ + int idSize = Types.getTypeSize(mType); + final int N = mNumEntries; + + ByteArrayInputStream bais = new ByteArrayInputStream(mData); + DataInputStream dis = new DataInputStream(bais); + State state = mHeap.mState; + + for (int i = 0; i < N; i++) { + long id; + + try { + if (idSize == 4) { + id = dis.readInt(); + } else { + id = dis.readLong(); + } + + Instance instance = state.findReference(id); + + if (instance != null) { + instance.visit(resultSet, filter); + } + } catch (java.io.IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public final String getTypeName() { + return Types.getTypeName(mType) + "[" + mNumEntries + "]"; + } + + public final String toString() { + return String.format("%s@0x08x", getTypeName(), mId); + } + + @Override + public String describeReferenceTo(long referent) { + // If this isn't an object array then we can't refer to an object + if (mType != Types.OBJECT) { + return super.describeReferenceTo(referent); + } + + int idSize = Types.getTypeSize(mType); + final int N = mNumEntries; + int numRefs = 0; + StringBuilder result = new StringBuilder("Elements ["); + ByteArrayInputStream bais = new ByteArrayInputStream(mData); + DataInputStream dis = new DataInputStream(bais); + + /* + * Spin through all the objects and build up a string describing + * all of the array elements that refer to the target object. + */ + for (int i = 0; i < N; i++) { + long id; + + try { + if (idSize == 4) { + id = dis.readInt(); + } else { + id = dis.readLong(); + } + + if (id == referent) { + numRefs++; + + if (numRefs > 1) { + result.append(", "); + } + + result.append(i); + } + } catch (java.io.IOException e) { + e.printStackTrace(); + } + } + + if (numRefs == 0) { + return super.describeReferenceTo(referent); + } + + result.append("]"); + + return result.toString(); + } +} diff --git a/hit/src/com/android/hit/ClassInstance.java b/hit/src/com/android/hit/ClassInstance.java new file mode 100644 index 000000000..36525bef3 --- /dev/null +++ b/hit/src/com/android/hit/ClassInstance.java @@ -0,0 +1,208 @@ +/* + * 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.hit; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Set; + +public class ClassInstance extends Instance { + private byte[] mFieldValues; + + public ClassInstance(long id, StackTrace stack, long classId) { + mId = id; + mStack = stack; + mClassId = classId; + } + + public final void loadFieldData(DataInputStream in, int numBytes) + throws IOException { + mFieldValues = new byte[numBytes]; + in.readFully(mFieldValues); + } + + @Override + public void resolveReferences(State state) { + ClassObj isa = mHeap.mState.findClass(mClassId); + + resolve(state, isa, isa.mStaticFieldTypes, isa.mStaticFieldValues); + resolve(state, isa, isa.mFieldTypes, mFieldValues); + } + + private void resolve(State state, ClassObj isa, int[] types, + byte[] values) { + ByteArrayInputStream bais = new ByteArrayInputStream(values); + DataInputStream dis = new DataInputStream(bais); + final int N = types.length; + + /* + * Spin through the list of fields, find all object references, + * and list ourselves as a reference holder. + */ + try { + for (int i = 0; i < N; i++) { + int type = types[i]; + int size = Types.getTypeSize(type); + + if (type == Types.OBJECT) { + long id; + + if (size == 4) { + id = dis.readInt(); + } else { + id = dis.readLong(); + } + + Instance instance = state.findReference(id); + + if (instance != null) { + instance.addParent(this); + } + } else { + dis.skipBytes(size); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public final int getSize() { + ClassObj isa = mHeap.mState.findClass(mClassId); + + return isa.getSize(); + } + + @Override + public final void visit(Set<Instance> resultSet, Filter filter) { + if (resultSet.contains(this)) { + return; + } + + if (filter != null) { + if (filter.accept(this)) { + resultSet.add(this); + } + } else { + resultSet.add(this); + } + + State state = mHeap.mState; + ClassObj isa = state.findClass(mClassId); + int[] types = isa.mFieldTypes; + ByteArrayInputStream bais = new ByteArrayInputStream(mFieldValues); + DataInputStream dis = new DataInputStream(bais); + final int N = types.length; + + /* + * Spin through the list of fields, find all object references, + * and list ourselves as a reference holder. + */ + try { + for (int i = 0; i < N; i++) { + int type = types[i]; + int size = Types.getTypeSize(type); + + if (type == Types.OBJECT) { + long id; + + if (size == 4) { + id = dis.readInt(); + } else { + id = dis.readLong(); + } + + Instance instance = state.findReference(id); + + if (instance != null) { + instance.visit(resultSet, filter); + } + } else { + dis.skipBytes(size); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public final String getTypeName() { + ClassObj theClass = mHeap.mState.findClass(mClassId); + + return theClass.mClassName; + } + + public final String toString() { + return String.format("%s@0x%08x", getTypeName(), mId); + } + + @Override + public String describeReferenceTo(long referent) { + ClassObj isa = mHeap.mState.findClass(mClassId); + int[] types = isa.mFieldTypes; + String[] fieldNames = isa.mFieldNames; + ByteArrayInputStream bais = new ByteArrayInputStream(mFieldValues); + DataInputStream dis = new DataInputStream(bais); + final int N = types.length; + StringBuilder result = new StringBuilder("Referenced in field(s):"); + int numReferences = 0; + + /* + * Spin through the list of fields, add info about the field + * references to the output text. + */ + try { + for (int i = 0; i < N; i++) { + int type = types[i]; + int size = Types.getTypeSize(type); + + if (type == Types.OBJECT) { + long id; + + if (size == 4) { + id = dis.readInt(); + } else { + id = dis.readLong(); + } + + if (id == referent) { + numReferences++; + result.append("\n "); + result.append(fieldNames[i]); + } + } else { + dis.skipBytes(size); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + /* + * TODO: perform a similar loop over the static fields of isa + */ + + if (numReferences == 0) { + return super.describeReferenceTo(referent); + } + + return result.toString(); + } +} diff --git a/hit/src/com/android/hit/ClassObj.java b/hit/src/com/android/hit/ClassObj.java new file mode 100644 index 000000000..388af4acf --- /dev/null +++ b/hit/src/com/android/hit/ClassObj.java @@ -0,0 +1,241 @@ +/* + * 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.hit; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public class ClassObj extends Instance implements Comparable<ClassObj> { + String mClassName; + long mSuperclassId; + + String[] mFieldNames; + int[] mFieldTypes; + + String[] mStaticFieldNames; + int[] mStaticFieldTypes; + byte[] mStaticFieldValues; + + ArrayList<Instance> mInstances = new ArrayList<Instance>(); + Set<ClassObj> mSubclasses = new HashSet<ClassObj>(); + + int mSize; + + public ClassObj(long id, StackTrace stack, String className) { + mId = id; + mStack = stack; + mClassName = className; + } + + @Override + public final void resolveReferences(State state) { + ByteArrayInputStream bais = + new ByteArrayInputStream(mStaticFieldValues); + DataInputStream dis = new DataInputStream(bais); + int[] types = mStaticFieldTypes; + final int N = types.length; + + /* + * Spin through the list of static fields, find all object references, + * and list ourselves as a reference holder. Also add them to + * the list of root objects. + */ + try { + for (int i = 0; i < N; i++) { + int type = types[i]; + int size = Types.getTypeSize(type); + + if (type == Types.OBJECT) { + long id; + + if (size == 4) { + id = dis.readInt(); + } else { + id = dis.readLong(); + } + + RootObj root = new RootObj(RootType.JAVA_STATIC, id); + + if (id == 0) { + root.mComment = String.format( + "Static field %s:%s null", + mClassName, + mStaticFieldNames[i]); + } else { + Instance instance = state.findReference(id); + + instance.addParent(this); + + root.mComment = String.format( + "Static field %s:%s %s [%s] 0x%08x", + mClassName, + mStaticFieldNames[i], + instance.getTypeName(), + instance.mHeap.mName, + id); + } + + mHeap.addRoot(root); + } else { + dis.skipBytes(size); + } + } + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + + // Lastly, add ourself as a subclass of our superclass + if (mSuperclassId != 0) { + ClassObj superclass = state.findClass(mSuperclassId); + + superclass.addSubclass(this); + } + } + + public final void addSubclass(ClassObj subclass) { + mSubclasses.add(subclass); + } + + public final void dumpSubclasses() { + for (ClassObj subclass: mSubclasses) { + System.out.println(" " + subclass.mClassName); + } + } + + public final String toString() { + return mClassName.replace('/', '.'); + } + + public final void addInstance(Instance instance) { + mInstances.add(instance); + } + + public final void setSuperclassId(long id) { + mSuperclassId = id; + } + + public final void setFieldNames(String[] names) { + mFieldNames = names; + } + + public final void setFieldTypes(int[] types) { + mFieldTypes = types; + } + + public final void setStaticFieldNames(String[] names) { + mStaticFieldNames = names; + } + + public final void setStaticFieldTypes(int[] types) { + mStaticFieldTypes = types; + } + + public final void setStaticFieldValues(byte[] values) { + mStaticFieldValues = values; + } + + public final void dump() { + System.out.println("+---------- ClassObj dump for: " + mClassName); + + System.out.println("+----- Static fields"); + + for (int i = 0; i < mStaticFieldNames.length; i++) { + System.out.println(mStaticFieldNames[i] + ": " + + mStaticFieldTypes[i]); + } + + System.out.println("+----- Instance fields"); + + for (int i = 0; i < mFieldNames.length; i++) { + System.out.println(mFieldNames[i] + ": " + mFieldTypes[i]); + } + } + + @Override + public final String getTypeName() { + return "class " + mClassName; + } + + @Override + public final void visit(Set<Instance> resultSet, Filter filter) { + if (resultSet.contains(this)) { + return; + } + + if (filter != null) { + if (filter.accept(this)) { + resultSet.add(this); + } + } else { + resultSet.add(this); + } + + ByteArrayInputStream bais = + new ByteArrayInputStream(mStaticFieldValues); + DataInputStream dis = new DataInputStream(bais); + int[] types = mStaticFieldTypes; + final int N = types.length; + State state = mHeap.mState; + + /* + * Spin through the list of static fields, find all object references, + * and visit them. + */ + try { + for (int i = 0; i < N; i++) { + int type = types[i]; + int size = Types.getTypeSize(type); + + if (type == Types.OBJECT) { + long id; + + if (size == 4) { + id = dis.readInt(); + } else { + id = dis.readLong(); + } + + Instance instance = state.findReference(id); + + if (instance != null) { + instance.visit(resultSet, filter); + } + } else { + dis.skipBytes(size); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public final int compareTo(ClassObj o) { + return mClassName.compareTo(o.mClassName); + } + + public final boolean equals(Object o) { + if (! (o instanceof ClassObj)) { + return false; + } + + return 0 == compareTo((ClassObj) o); + } +} diff --git a/hit/src/com/android/hit/Heap.java b/hit/src/com/android/hit/Heap.java new file mode 100644 index 000000000..82dde8eb1 --- /dev/null +++ b/hit/src/com/android/hit/Heap.java @@ -0,0 +1,185 @@ +/* + * 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.hit; + +import java.util.ArrayList; +import java.util.HashMap; + +public class Heap { + String mName; + + // List of individual stack frames + HashMap<Long, StackFrame> mFrames = new HashMap<Long, StackFrame>(); + + // List stack traces, which are lists of stack frames + HashMap<Integer, StackTrace> mTraces = new HashMap<Integer, StackTrace>(); + + // Root objects such as interned strings, jni locals, etc + ArrayList<RootObj> mRoots = new ArrayList<RootObj>(); + + // List of threads + HashMap<Integer, ThreadObj> mThreads = new HashMap<Integer, ThreadObj>(); + + // Class definitions + HashMap<Long, ClassObj> mClassesById = new HashMap<Long, ClassObj>(); + HashMap<String, ClassObj> mClassesByName = new HashMap<String, ClassObj>(); + + // List of instances of above class definitions + HashMap<Long, Instance> mInstances = new HashMap<Long, Instance>(); + + // The super-state that this heap is part of + State mState; + + public Heap(String name) { + mName = name; + } + + public final void addStackFrame(StackFrame theFrame) { + mFrames.put(theFrame.mId, theFrame); + } + + public final StackFrame getStackFrame(long id) { + return mFrames.get(id); + } + + public final void addStackTrace(StackTrace theTrace) { + mTraces.put(theTrace.mSerialNumber, theTrace); + } + + public final StackTrace getStackTrace(int traceSerialNumber) { + return mTraces.get(traceSerialNumber); + } + + public final StackTrace getStackTraceAtDepth(int traceSerialNumber, + int depth) { + StackTrace trace = mTraces.get(traceSerialNumber); + + if (trace != null) { + trace = trace.fromDepth(depth); + } + + return trace; + } + + public final void addRoot(RootObj root) { + root.mIndex = mRoots.size(); + mRoots.add(root); + } + + public final void addThread(ThreadObj thread, int serialNumber) { + mThreads.put(serialNumber, thread); + } + + public final ThreadObj getThread(int serialNumber) { + return mThreads.get(serialNumber); + } + + public final void addInstance(long id, Instance instance) { + mInstances.put(id, instance); + } + + public final Instance getInstance(long id) { + return mInstances.get(id); + } + + public final void addClass(long id, ClassObj theClass) { + mClassesById.put(id, theClass); + mClassesByName.put(theClass.mClassName, theClass); + } + + public final ClassObj getClass(long id) { + return mClassesById.get(id); + } + + public final ClassObj getClass(String name) { + return mClassesByName.get(name); + } + + public final void dumpInstanceCounts() { + for (ClassObj theClass: mClassesById.values()) { + int count = theClass.mInstances.size(); + + if (count > 0) { + System.out.println(theClass + ": " + count); + } + } + } + + public final void dumpSubclasses() { + for (ClassObj theClass: mClassesById.values()) { + int count = theClass.mSubclasses.size(); + + if (count > 0) { + System.out.println(theClass); + theClass.dumpSubclasses(); + } + } + } + + public final void dumpSizes() { + for (ClassObj theClass: mClassesById.values()) { + int size = 0; + + for (Instance instance: theClass.mInstances) { + size += instance.getCompositeSize(); + } + + if (size > 0) { + System.out.println(theClass + ": base " + theClass.getSize() + + ", composite " + size); + } + } + } + + /* + * Spin through all of the class instances and link them to their + * parent class definition objects. Then have each instance resolve + * its own internal object references. + */ + public final void resolveInstanceRefs(State state) { + for (Instance instance : mInstances.values()) { + ClassObj theClass = mClassesById.get(instance.mClassId); + + if (theClass == null) { + continue; + } + + String name = theClass.mClassName; + String superclassName = "none"; + ClassObj superClass = mClassesById.get(theClass.mSuperclassId); + + if (superClass != null) { + superclassName = superClass.mClassName; + } + + theClass.addInstance(instance); + instance.resolveReferences(state); + } + } + + public final void resolveClassStatics(State state) { + for (ClassObj theClass: mClassesById.values()) { + theClass.resolveReferences(state); + } + } + + public final void resolveRoots(State state) { + for (RootObj root: mRoots) { + root.resolveReferences(state); + } + } +} diff --git a/hit/src/com/android/hit/HprofParser.java b/hit/src/com/android/hit/HprofParser.java new file mode 100644 index 000000000..2226001ff --- /dev/null +++ b/hit/src/com/android/hit/HprofParser.java @@ -0,0 +1,611 @@ +/* + * 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.hit; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.InputStream; +import java.io.EOFException; +import java.io.IOException; +import java.util.HashMap; + +public class HprofParser +{ + private static final int STRING_IN_UTF8 = 0x01; + private static final int LOAD_CLASS = 0x02; + private static final int UNLOAD_CLASS = 0x03; // unused + private static final int STACK_FRAME = 0x04; + private static final int STACK_TRACE = 0x05; + private static final int ALLOC_SITES = 0x06; // unused + private static final int HEAP_SUMMARY = 0x07; + private static final int START_THREAD = 0x0a; // unused + private static final int END_THREAD = 0x0b; // unused + private static final int HEAP_DUMP = 0x0c; + private static final int HEAP_DUMP_SEGMENT = 0x1c; + private static final int HEAP_DUMP_END = 0x2c; + private static final int CPU_SAMPLES = 0x0d; // unused + private static final int CONTROL_SETTINGS = 0x0e; // unused + + 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; + + DataInputStream mInput; + int mIdSize; + State mState; + + byte[] mFieldBuffer = new byte[8]; + + /* + * These are only needed while parsing so are not kept as part of the + * heap data. + */ + HashMap<Long, String> mStrings = new HashMap<Long, String>(); + HashMap<Long, String> mClassNames = new HashMap<Long, String>(); + + public HprofParser(DataInputStream in) { + mInput = in; + } + + public final State parse() { + State state = new State(); + mState = state; + + try { + String s = readNullTerminatedString(); + DataInputStream in = mInput; + + mIdSize = in.readInt(); + Types.setIdSize(mIdSize); + + in.readLong(); // Timestamp, ignored for now + + while (true) { + int tag = in.readUnsignedByte(); + int timestamp = in.readInt(); + int length = in.readInt(); + + switch (tag) { + case STRING_IN_UTF8: + loadString(length - 4); + break; + + case LOAD_CLASS: + loadClass(); + break; + + case STACK_FRAME: + loadStackFrame(); + break; + + case STACK_TRACE: + loadStackTrace(); + break; + + case HEAP_DUMP: + loadHeapDump(length); + mState.setToDefaultHeap(); + break; + + case HEAP_DUMP_SEGMENT: + loadHeapDump(length); + mState.setToDefaultHeap(); + break; + + default: + skipFully(length); + } + + } + } catch (EOFException eof) { + // this is fine + } catch (Exception e) { + e.printStackTrace(); + } + + mState.resolveReferences(); + + return state; + } + + private String readNullTerminatedString() throws IOException { + StringBuilder s = new StringBuilder(); + DataInputStream in = mInput; + + for (int c = in.read(); c != 0; c = in.read()) { + s.append((char) c); + } + + return s.toString(); + } + + private long readId() throws IOException { + switch (mIdSize) { + case 1: return mInput.readUnsignedByte(); + case 2: return mInput.readUnsignedShort(); + case 4: return ((long) mInput.readInt()) & 0x00000000ffffffffL; + case 8: return mInput.readLong(); + } + + throw new IllegalArgumentException("ID Length must be 1, 2, 4, or 8"); + } + + private String readUTF8(int length) throws IOException { + byte[] b = new byte[length]; + + mInput.read(b); + + return new String(b, "utf-8"); + } + + private void loadString(int length) throws IOException { + long id = readId(); + String string = readUTF8(length); + + mStrings.put(id, string); + } + + private void loadClass() throws IOException { + DataInputStream in = mInput; + int serial = in.readInt(); + long id = readId(); + int stackTrace = in.readInt(); // unused + 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); + + mState.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] = mState.getStackFrame(readId()); + } + + StackTrace trace = new StackTrace(serialNumber, threadSerialNumber, + frames); + + mState.addStackTrace(trace); + } + + private void loadHeapDump(int length) throws IOException { + DataInputStream in = mInput; + + while (length > 0) { + int tag = in.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); + + mState.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.available() + + " bytes possibly remaining"); + } + } + } + + private int loadJniLocal() throws IOException { + long id = readId(); + int threadSerialNumber = mInput.readInt(); + int stackFrameNumber = mInput.readInt(); + ThreadObj thread = mState.getThread(threadSerialNumber); + StackTrace trace = mState.getStackTraceAtDepth(thread.mStackTrace, + stackFrameNumber); + RootObj root = new RootObj(RootType.NATIVE_LOCAL, id, + threadSerialNumber, trace); + + root.setHeap(mState.mCurrentHeap); + mState.addRoot(root); + + return mIdSize + 4 + 4; + } + + private int loadJavaFrame() throws IOException { + long id = readId(); + int threadSerialNumber = mInput.readInt(); + int stackFrameNumber = mInput.readInt(); + ThreadObj thread = mState.getThread(threadSerialNumber); + StackTrace trace = mState.getStackTraceAtDepth(thread.mStackTrace, + stackFrameNumber); + RootObj root = new RootObj(RootType.JAVA_LOCAL, id, threadSerialNumber, + trace); + + root.setHeap(mState.mCurrentHeap); + mState.addRoot(root); + + return mIdSize + 4 + 4; + } + + private int loadNativeStack() throws IOException { + long id = readId(); + int threadSerialNumber = mInput.readInt(); + ThreadObj thread = mState.getThread(threadSerialNumber); + StackTrace trace = mState.getStackTrace(thread.mStackTrace); + RootObj root = new RootObj(RootType.NATIVE_STACK, id, + threadSerialNumber, trace); + + root.setHeap(mState.mCurrentHeap); + mState.addRoot(root); + + return mIdSize + 4; + } + + private int loadBasicObj(RootType type) throws IOException { + long id = readId(); + RootObj root = new RootObj(type, id); + + root.setHeap(mState.mCurrentHeap); + mState.addRoot(root); + + return mIdSize; + } + + private int loadThreadBlock() throws IOException { + long id = readId(); + int threadSerialNumber = mInput.readInt(); + ThreadObj thread = mState.getThread(threadSerialNumber); + StackTrace stack = mState.getStackTrace(thread.mStackTrace); + RootObj root = new RootObj(RootType.THREAD_BLOCK, id, + threadSerialNumber, stack); + + root.setHeap(mState.mCurrentHeap); + mState.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); + + mState.addThread(thread, threadSerialNumber); + + return mIdSize + 4 + 4; + } + + private int loadClassDump() throws IOException { + int bytesRead = 0; + DataInputStream in = mInput; + long id = readId(); + int stackSerialNumber = in.readInt(); + StackTrace stack = mState.getStackTrace(stackSerialNumber); + long superClassId = readId(); + long classLoaderId = readId(); + long signersId = readId(); + long protectionDomainId = readId(); + long reserved1 = readId(); + long reserved2 = readId(); + int instanceSize = in.readInt(); + + bytesRead = (7 * mIdSize) + 4 + 4; + + // Skip over the constant pool + int numEntries = in.readUnsignedShort(); + bytesRead += 2; + + for (int i = 0; i < numEntries; i++) { + in.readUnsignedShort(); + bytesRead += 2 + skipValue(); + } + + // Static fields + numEntries = in.readUnsignedShort(); + bytesRead += 2; + + String[] staticFieldNames = new String[numEntries]; + int[] staticFieldTypes = new int[numEntries]; + ByteArrayOutputStream staticFieldValues = new ByteArrayOutputStream(); + byte[] buffer = mFieldBuffer; + + for (int i = 0; i < numEntries; i++) { + staticFieldNames[i] = mStrings.get(readId()); + + int fieldType = in.readByte(); + int fieldSize = Types.getTypeSize(fieldType); + staticFieldTypes[i] = fieldType; + + in.readFully(buffer, 0, fieldSize); + staticFieldValues.write(buffer, 0, fieldSize); + + bytesRead += mIdSize + 1 + fieldSize; + } + + // Instance fields + numEntries = in.readUnsignedShort(); + bytesRead += 2; + + String[] names = new String[numEntries]; + int[] types = new int[numEntries]; + + for (int i = 0; i < numEntries; i++) { + long fieldName = readId(); + int type = in.readUnsignedByte(); + + names[i] = mStrings.get(fieldName); + types[i] = type; + + bytesRead += mIdSize + 1; + } + + ClassObj theClass = new ClassObj(id, stack, mClassNames.get(id)); + + theClass.setStaticFieldNames(staticFieldNames); + theClass.setStaticFieldTypes(staticFieldTypes); + theClass.setStaticFieldValues(staticFieldValues.toByteArray()); + + theClass.setSuperclassId(superClassId); + theClass.setFieldNames(names); + theClass.setFieldTypes(types); + theClass.setSize(instanceSize); + + theClass.setHeap(mState.mCurrentHeap); + + mState.addClass(id, theClass); + + return bytesRead; + } + + private int loadInstanceDump() throws IOException { + long id = readId(); + int stackId = mInput.readInt(); + StackTrace stack = mState.getStackTrace(stackId); + long classId = readId(); + int remaining = mInput.readInt(); + ClassInstance instance = new ClassInstance(id, stack, classId); + + instance.loadFieldData(mInput, remaining); + instance.setHeap(mState.mCurrentHeap); + mState.addInstance(id, instance); + + return mIdSize + 4 + mIdSize + 4 + remaining; + } + + private int loadObjectArrayDump() throws IOException { + long id = readId(); + int stackId = mInput.readInt(); + StackTrace stack = mState.getStackTrace(stackId); + int numElements = mInput.readInt(); + long classId = readId(); + int totalBytes = numElements * mIdSize; + byte[] data = new byte[totalBytes]; + String className = mClassNames.get(classId); + + mInput.readFully(data); + + ArrayInstance array = new ArrayInstance(id, stack, Types.OBJECT, + numElements, data); + + array.mClassId = classId; + array.setHeap(mState.mCurrentHeap); + mState.addInstance(id, array); + + return mIdSize + 4 + 4 + mIdSize + totalBytes; + } + + private int loadPrimitiveArrayDump() throws IOException { + long id = readId(); + int stackId = mInput.readInt(); + StackTrace stack = mState.getStackTrace(stackId); + int numElements = mInput.readInt(); + int type = mInput.readUnsignedByte(); + int size = Types.getTypeSize(type); + int totalBytes = numElements * size; + byte[] data = new byte[totalBytes]; + + mInput.readFully(data); + + ArrayInstance array = new ArrayInstance(id, stack, type, numElements, + data); + + array.setHeap(mState.mCurrentHeap); + mState.addInstance(id, array); + + return mIdSize + 4 + 4 + 1 + totalBytes; + } + + private int loadJniMonitor() throws IOException { + long id = readId(); + int threadSerialNumber = mInput.readInt(); + int stackDepth = mInput.readInt(); + ThreadObj thread = mState.getThread(threadSerialNumber); + StackTrace trace = mState.getStackTraceAtDepth(thread.mStackTrace, + stackDepth); + RootObj root = new RootObj(RootType.NATIVE_MONITOR, id, + threadSerialNumber, trace); + + root.setHeap(mState.mCurrentHeap); + mState.addRoot(root); + + return mIdSize + 4 + 4; + } + + private int skipValue() throws IOException { + int type = mInput.readUnsignedByte(); + int size = Types.getTypeSize(type); + + skipFully(size); + + return size + 1; + } + + /* + * BufferedInputStream will not skip(int) the entire requested number + * of bytes if it extends past the current buffer boundary. So, this + * routine is needed to actually skip over the requested number of bytes + * using as many iterations as needed. + */ + private void skipFully(long numBytes) throws IOException { + while (numBytes > 0) { + long skipped = mInput.skip(numBytes); + + numBytes -= skipped; + } + } +} diff --git a/hit/src/com/android/hit/Instance.java b/hit/src/com/android/hit/Instance.java new file mode 100644 index 000000000..24db38d1b --- /dev/null +++ b/hit/src/com/android/hit/Instance.java @@ -0,0 +1,117 @@ +/* + * 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.hit; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public abstract class Instance { + long mId; + + // Id of the ClassObj of which this object is an instance + long mClassId; + + // The stack in which this object was allocated + StackTrace mStack; + + // The heap in which this object was allocated (app, zygote, etc) + Heap mHeap; + + // The size of this object + int mSize; + + public interface Filter { + public boolean accept(Instance instance); + } + + // List of all objects that hold a live reference to this object + private ArrayList<Instance> mParents; + + /* + * After the whole HPROF file is read and parsed this method will be + * called on all heap objects so that they can resolve their internal + * object references. + * + * The super-State is passed in because some object references (such + * as interned Strings and static class fields) may need to be searched + * for in a heap other than the one this Instance is in. + */ + public abstract void resolveReferences(State state); + + /* + * Some operations require gathering all the objects in a given section + * of the object graph. If non-null, the filter is applied to each + * node in the graph to determine if it should be added to the result + * set. + */ + public abstract void visit(Set<Instance> resultSet, Filter filter); + + public void setSize(int size) { + mSize = size; + } + + public final int getCompositeSize() { + HashSet<Instance> set = new HashSet<Instance>(); + + visit(set, null); + + int size = 0; + + for (Instance instance: set) { + size += instance.getSize(); + } + + return size; + } + + // Returns the instrinsic size of a given object + public int getSize() { + return mSize; + } + + public abstract String getTypeName(); + + public void setHeap(Heap heap) { + mHeap = heap; + } + + // Add to the list of objects that have a hard reference to this Instance + public void addParent(Instance parent) { + if (mParents == null) { + mParents = new ArrayList<Instance>(); + } + + mParents.add(parent); + } + + public ArrayList<Instance> getParents() { + if (mParents == null) { + mParents = new ArrayList<Instance>(); + } + + return mParents; + } + + /* + * If this object has a reference to the object identified by id, return + * a String describing the reference in detail. + */ + public String describeReferenceTo(long id) { + return "No reference to 0x" + Long.toHexString(id); + } +} diff --git a/hit/src/com/android/hit/Main.java b/hit/src/com/android/hit/Main.java new file mode 100644 index 000000000..eebadfe1a --- /dev/null +++ b/hit/src/com/android/hit/Main.java @@ -0,0 +1,97 @@ +/* + * 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.hit; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.util.Map; +import java.util.Set; + +public class Main +{ + public static void main(String argv[]) { + FileInputStream fis; + BufferedInputStream bis; + DataInputStream dis; + + try { + fis = new FileInputStream(argv[0]); + bis = new BufferedInputStream(fis); + dis = new DataInputStream(bis); + + State state = (new HprofParser(dis)).parse(); + + dis.close(); + + testClassesQuery(state); + testAllClassesQuery(state); + testFindInstancesOf(state); + testFindAllInstancesOf(state); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void testClassesQuery(State state) { + String[] x = new String[] { + "char[", + "javax.", + "org.xml.sax" + }; + + Map<String, Set<ClassObj>> someClasses = Queries.classes(state, x); + + for (String thePackage: someClasses.keySet()) { + System.out.println("------------------- " + thePackage); + + Set<ClassObj> classes = someClasses.get(thePackage); + + for (ClassObj theClass: classes) { + System.out.println(" " + theClass.mClassName); + } + } + } + + private static void testAllClassesQuery(State state) { + Map<String, Set<ClassObj>> allClasses = Queries.allClasses(state); + + for (String thePackage: allClasses.keySet()) { + System.out.println("------------------- " + thePackage); + + Set<ClassObj> classes = allClasses.get(thePackage); + + for (ClassObj theClass: classes) { + System.out.println(" " + theClass.mClassName); + } + } + } + + private static void testFindInstancesOf(State state) { + Instance[] instances = Queries.instancesOf(state, "java.lang.String"); + + System.out.println("There are " + instances.length + " Strings."); + } + + private static void testFindAllInstancesOf(State state) { + Instance[] instances = Queries.allInstancesOf(state, + "android.graphics.drawable.Drawable"); + + System.out.println("There are " + instances.length + + " instances of Drawables and its subclasses."); + } +} diff --git a/hit/src/com/android/hit/Queries.java b/hit/src/com/android/hit/Queries.java new file mode 100644 index 000000000..cc692b043 --- /dev/null +++ b/hit/src/com/android/hit/Queries.java @@ -0,0 +1,239 @@ +/* + * 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.hit; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +public class Queries { + /* + * NOTES: Here's a list of the queries that can be done in hat and + * how you'd perform a similar query here in hit: + * + * hat hit + * ------------------------------------------------------------------------ + * allClasses classes + * allClassesWithPlatform allClasses + * class findClass + * instances instancesOf + * allInstances allInstancesOf + * object findObject + * showRoots getRoots + * newInstances newInstances + * + * reachableFrom make a call to findObject to get the target + * parent object, this will give you an Instance. + * Then call visit(Set, Filter) on that to have + * it build the set of objects in its subgraph. + * + * rootsTo make a call to findObject on the leaf node + * in question, this will give you an Instance. + * Instances have an ArrayList of all of the + * parent objects that refer to it. You can + * follow those parent links until you hit an + * object whose parent is null or a ThreadObj. + * You've not successfully traced the paths to + * the roots. + */ + + private static final String DEFAULT_PACKAGE = "<default>"; + + /* + * Produce a collection of all classes, broken down by package. + * The keys of the resultant map iterate in sorted package order. + * The values of the map are the classes defined in each package. + */ + public static Map<String, Set<ClassObj>> allClasses(State state) { + return classes(state, null); + } + + public static Map<String, Set<ClassObj>> classes(State state, + String[] excludedPrefixes) { + TreeMap<String, Set<ClassObj>> result = + new TreeMap<String, Set<ClassObj>>(); + + Set<ClassObj> classes = new TreeSet<ClassObj>(); + + // Build a set of all classes across all heaps + for (Heap heap: state.mHeaps.values()) { + classes.addAll(heap.mClassesById.values()); + } + + // Filter it if needed + if (excludedPrefixes != null) { + final int N = excludedPrefixes.length; + Iterator<ClassObj> iter = classes.iterator(); + + while (iter.hasNext()) { + ClassObj theClass = iter.next(); + String classPath = theClass.toString(); + + for (int i = 0; i < N; i++) { + if (classPath.startsWith(excludedPrefixes[i])) { + iter.remove(); + break; + } + } + } + } + + // Now that we have a final list of classes, group them by package + for (ClassObj theClass: classes) { + String packageName = DEFAULT_PACKAGE; + int lastDot = theClass.mClassName.lastIndexOf('.'); + + if (lastDot != -1) { + packageName = theClass.mClassName.substring(0, lastDot); + } + + Set<ClassObj> classSet = result.get(packageName); + + if (classSet == null) { + classSet = new TreeSet<ClassObj>(); + result.put(packageName, classSet); + } + + classSet.add(theClass); + } + + return result; + } + + /* + * It's sorta sad that this is a pass-through call, but it seems like + * having all of the hat-like query methods in one place is a good thing + * even if there is duplication of effort. + */ + public static ClassObj findClass(State state, String name) { + return state.findClass(name); + } + + /* + * Return an array of instances of the given class. This does not include + * instances of subclasses. + */ + public static Instance[] instancesOf(State state, String baseClassName) { + ClassObj theClass = state.findClass(baseClassName); + + if (theClass == null) { + throw new IllegalArgumentException("Class not found: " + + baseClassName); + } + + Instance[] instances = new Instance[theClass.mInstances.size()]; + + return theClass.mInstances.toArray(instances); + } + + /* + * Return an array of instances of the given class. This includes + * instances of subclasses. + */ + public static Instance[] allInstancesOf(State state, String baseClassName) { + ClassObj theClass = state.findClass(baseClassName); + + if (theClass == null) { + throw new IllegalArgumentException("Class not found: " + + baseClassName); + } + + ArrayList<ClassObj> classList = new ArrayList<ClassObj>(); + + classList.add(theClass); + classList.addAll(traverseSubclasses(theClass)); + + ArrayList<Instance> instanceList = new ArrayList<Instance>(); + + for (ClassObj someClass: classList) { + instanceList.addAll(someClass.mInstances); + } + + Instance[] result = new Instance[instanceList.size()]; + + instanceList.toArray(result); + + return result; + } + + private static ArrayList<ClassObj> traverseSubclasses(ClassObj base) { + ArrayList<ClassObj> result = new ArrayList<ClassObj>(); + + for (ClassObj subclass: base.mSubclasses) { + result.add(subclass); + result.addAll(traverseSubclasses(subclass)); + } + + return result; + } + + /* + * Find a reference to an object based on its id. The id should be + * in hexadecimal. + */ + public static Instance findObject(State state, String id) { + long id2 = Long.parseLong(id, 16); + + return state.findReference(id2); + } + + public static Collection<RootObj> getRoots(State state) { + HashSet<RootObj> result = new HashSet<RootObj>(); + + for (Heap heap: state.mHeaps.values()) { + result.addAll(heap.mRoots); + } + + return result; + } + + public static final Instance[] newInstances(State older, State newer) { + ArrayList<Instance> resultList = new ArrayList<Instance>(); + + for (Heap newHeap: newer.mHeaps.values()) { + Heap oldHeap = older.getHeap(newHeap.mName); + + if (oldHeap == null) { + continue; + } + + for (Instance instance: newHeap.mInstances.values()) { + Instance oldInstance = oldHeap.getInstance(instance.mId); + + /* + * If this instance wasn't in the old heap, or was there, + * but that ID was for an obj of a different type, then we have + * a newly allocated object and we should report it in the + * results. + */ + if ((oldInstance == null) + || (instance.mClassId != oldInstance.mClassId)) { + resultList.add(instance); + } + } + } + + Instance[] resultArray = new Instance[resultList.size()]; + + return resultList.toArray(resultArray); + } +} diff --git a/hit/src/com/android/hit/RootObj.java b/hit/src/com/android/hit/RootObj.java new file mode 100644 index 000000000..1f9d53912 --- /dev/null +++ b/hit/src/com/android/hit/RootObj.java @@ -0,0 +1,111 @@ +/* + * 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.hit; + +import java.util.Set; + +public class RootObj extends Instance { + RootType mType = RootType.UNKNOWN; + int mIndex; + int mThread; + + /* + * These two fields are only used by roots that are static + * fields of class objects + */ + long mParent; + String mComment; + + public RootObj(RootType type) { + this(type, 0, 0, null); + } + + public RootObj(RootType type, long id) { + this(type, id, 0, null); + } + + public RootObj(RootType type, long id, int thread, StackTrace stack) { + mType = type; + mId = id; + mThread = thread; + mStack = stack; + } + + public final String getClassName(State state) { + ClassObj theClass; + + if (mType == RootType.SYSTEM_CLASS) { + theClass = state.findClass(mId); + } else { + Instance instance = state.findReference(mId); + + theClass = state.findClass(instance.mClassId); + } + + if (theClass == null) { + return "no class defined!!"; + } + + return theClass.mClassName; + } + + @Override + public final int getSize() { + Instance instance = null; + + if (mType == RootType.SYSTEM_CLASS) { + instance = mHeap.mState.findClass(mId); + } else { + instance = mHeap.mState.findReference(mId); + } + + if (instance == null) { + return 0; + } + + return instance.getSize(); + } + + @Override + public final void visit(Set<Instance> resultSet, Filter filter) { + if (resultSet.contains(this)) { + return; + } + + if (filter != null) { + if (filter.accept(this)) { + resultSet.add(this); + } + } else { + resultSet.add(this); + } + } + + @Override + public final void resolveReferences(State state) { + // Nothing to do here + } + + @Override + public final String getTypeName() { + return "root " + mType.getName(); + } + + public final String toString() { + return String.format("%s@0x08x", mType.getName(), mId); + } +} diff --git a/hit/src/com/android/hit/RootType.java b/hit/src/com/android/hit/RootType.java new file mode 100644 index 000000000..01ebefff6 --- /dev/null +++ b/hit/src/com/android/hit/RootType.java @@ -0,0 +1,53 @@ +/* + * 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.hit; + +public enum RootType { + UNREACHABLE (0, "unreachable object"), + INVALID_TYPE (1, "invalid type"), + INTERNED_STRING (2, "interned string"), + UNKNOWN (3, "unknown"), + SYSTEM_CLASS (4, "system class"), + VM_INTERNAL (5, "vm internal"), + DEBUGGER (6, "debugger"), + NATIVE_LOCAL (7, "native local"), + NATIVE_STATIC (8, "native static"), + THREAD_BLOCK (9, "thread block"), + BUSY_MONITOR (10, "busy monitor"), + NATIVE_MONITOR (11, "native monitor"), + REFERENCE_CLEANUP (12, "reference cleanup"), + FINALIZING (13, "finalizing"), + JAVA_LOCAL (14, "java local"), + NATIVE_STACK (15, "native stack"), + JAVA_STATIC (16, "java static"); + + private final int mType; + private final String mName; + + RootType(int type, String name) { + mType = type; + mName = name; + } + + public final int getType() { + return mType; + } + + public final String getName() { + return mName; + } +} diff --git a/hit/src/com/android/hit/StackFrame.java b/hit/src/com/android/hit/StackFrame.java new file mode 100644 index 000000000..a40f607e3 --- /dev/null +++ b/hit/src/com/android/hit/StackFrame.java @@ -0,0 +1,60 @@ +/* + * 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.hit; + +public class StackFrame { + public static final int NO_LINE_NUMBER = 0; + public static final int UNKNOWN_LOCATION = -1; + public static final int COMPILED_METHOD = -2; + public static final int NATIVE_METHOD = -3; + + long mId; + String mMethodName; + String mSignature; + String mFilename; + int mSerialNumber; + int mLineNumber; + + public StackFrame(long id, String method, String sig, String file, + int serial, int line) { + mId = id; + mMethodName = method; + mSignature = sig; + mFilename = file; + mSerialNumber = serial; + mLineNumber = line; + } + + private final String lineNumberString() { + switch (mLineNumber) { + case NO_LINE_NUMBER: return "No line number"; + case UNKNOWN_LOCATION: return "Unknown line number"; + case COMPILED_METHOD: return "Compiled method"; + case NATIVE_METHOD: return "Native method"; + + default: return String.valueOf(mLineNumber); + } + } + + public final String toString() { + return mMethodName + + mSignature.replace('/', '.') + + " - " + + mFilename + ":" + + lineNumberString(); + } +} diff --git a/hit/src/com/android/hit/StackTrace.java b/hit/src/com/android/hit/StackTrace.java new file mode 100644 index 000000000..239a7b818 --- /dev/null +++ b/hit/src/com/android/hit/StackTrace.java @@ -0,0 +1,64 @@ +/* + * 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.hit; + +public class StackTrace { + int mSerialNumber; + int mThreadSerialNumber; + StackFrame[] mFrames; + + /* + * For subsets of the stack frame we'll reference the parent list of frames + * but keep track of its offset into the parent's list of stack frame ids. + * This alleviates the need to constantly be duplicating subsections of the + * list of stack frame ids. + */ + StackTrace mParent = null; + int mOffset = 0; + + private StackTrace() { + + } + + public StackTrace(int serial, int thread, StackFrame[] frames) { + mSerialNumber = serial; + mThreadSerialNumber = thread; + mFrames = frames; + } + + public final StackTrace fromDepth(int startingDepth) { + StackTrace result = new StackTrace(); + + if (mParent != null) { + result.mParent = mParent; + } else { + result.mParent = this; + } + + result.mOffset = startingDepth + mOffset; + + return result; + } + + public final void dump() { + final int N = mFrames.length; + + for (int i = 0; i < N; i++) { + System.out.println(mFrames[i].toString()); + } + } +} diff --git a/hit/src/com/android/hit/State.java b/hit/src/com/android/hit/State.java new file mode 100644 index 000000000..9b2060d41 --- /dev/null +++ b/hit/src/com/android/hit/State.java @@ -0,0 +1,179 @@ +/* + * 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.hit; + +import java.util.ArrayList; +import java.util.HashMap; + +/* + * State is a snapshot of all of the heaps, and related meta-data, for + * the runtime at a given instant. + * + * During parsing of the HPROF file HEAP_DUMP_INFO chunks change which heap + * is being referenced. + */ +public class State { + HashMap<Integer, Heap> mHeaps; + Heap mCurrentHeap; + + public State() { + mHeaps = new HashMap<Integer, Heap>(); + setToDefaultHeap(); + } + + public Heap setToDefaultHeap() { + return setHeapTo(0, "default"); + } + + public Heap setHeapTo(int id, String name) { + Heap heap = mHeaps.get(id); + + if (heap == null) { + heap = new Heap(name); + heap.mState = this; + mHeaps.put(id, heap); + } + + mCurrentHeap = heap; + + return mCurrentHeap; + } + + public Heap getHeap(int id) { + return mHeaps.get(id); + } + + public Heap getHeap(String name) { + for (Heap heap: mHeaps.values()) { + if (heap.mName.equals(name)) { + return heap; + } + } + + return null; + } + + public final void addStackFrame(StackFrame theFrame) { + mCurrentHeap.addStackFrame(theFrame); + } + + public final StackFrame getStackFrame(long id) { + return mCurrentHeap.getStackFrame(id); + } + + public final void addStackTrace(StackTrace theTrace) { + mCurrentHeap.addStackTrace(theTrace); + } + + public final StackTrace getStackTrace(int traceSerialNumber) { + return mCurrentHeap.getStackTrace(traceSerialNumber); + } + + public final StackTrace getStackTraceAtDepth(int traceSerialNumber, + int depth) { + return mCurrentHeap.getStackTraceAtDepth(traceSerialNumber, depth); + } + + public final void addRoot(RootObj root) { + mCurrentHeap.addRoot(root); + } + + public final void addThread(ThreadObj thread, int serialNumber) { + mCurrentHeap.addThread(thread, serialNumber); + } + + public final ThreadObj getThread(int serialNumber) { + return mCurrentHeap.getThread(serialNumber); + } + + public final void addInstance(long id, Instance instance) { + mCurrentHeap.addInstance(id, instance); + } + + public final void addClass(long id, ClassObj theClass) { + mCurrentHeap.addClass(id, theClass); + } + + public final Instance findReference(long id) { + for (Heap heap: mHeaps.values()) { + Instance instance = heap.getInstance(id); + + if (instance != null) { + return instance; + } + } + + // Couldn't find an instance of a class, look for a class object + return findClass(id); + } + + public final ClassObj findClass(long id) { + for (Heap heap: mHeaps.values()) { + ClassObj theClass = heap.getClass(id); + + if (theClass != null) { + return theClass; + } + } + + return null; + } + + public final ClassObj findClass(String name) { + for (Heap heap: mHeaps.values()) { + ClassObj theClass = heap.getClass(name); + + if (theClass != null) { + return theClass; + } + } + + return null; + } + + public final void dumpInstanceCounts() { + for (Heap heap: mHeaps.values()) { + System.out.println( + "+------------------ instance counts for heap: " + heap.mName); + heap.dumpInstanceCounts(); + } + } + + public final void dumpSizes() { + for (Heap heap: mHeaps.values()) { + System.out.println( + "+------------------ sizes for heap: " + heap.mName); + heap.dumpSizes(); + } + } + + public final void dumpSubclasses() { + for (Heap heap: mHeaps.values()) { + System.out.println( + "+------------------ subclasses for heap: " + heap.mName); + heap.dumpSubclasses(); + } + } + + public final void resolveReferences() { + for (Heap heap: mHeaps.values()) { + heap.resolveInstanceRefs(this); + heap.resolveClassStatics(this); + heap.resolveRoots(this); + } + } +} diff --git a/hit/src/com/android/hit/ThreadObj.java b/hit/src/com/android/hit/ThreadObj.java new file mode 100644 index 000000000..32a73f024 --- /dev/null +++ b/hit/src/com/android/hit/ThreadObj.java @@ -0,0 +1,27 @@ +/* + * 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.hit; + +public class ThreadObj { + long mId; + int mStackTrace; + + public ThreadObj(long id, int stackTrace) { + mId = id; + mStackTrace = stackTrace; + } +} diff --git a/hit/src/com/android/hit/Types.java b/hit/src/com/android/hit/Types.java new file mode 100644 index 000000000..ef316a94b --- /dev/null +++ b/hit/src/com/android/hit/Types.java @@ -0,0 +1,89 @@ +/* + * 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.hit; + +public class Types { + private static int mIdSize = 4; + + public static final int OBJECT = 2; + public static final int BOOLEAN = 4; + public static final int CHAR = 5; + public static final int FLOAT = 6; + public static final int DOUBLE = 7; + public static final int BYTE = 8; + public static final int SHORT = 9; + public static final int INT = 10; + public static final int LONG = 11; + + public static final void setIdSize(int size) { + mIdSize = size; + } + + public static final int getTypeSize(int type) { + switch (type) { + case '[': return mIdSize; // array object + case 'L': return mIdSize; // object + case 'Z': return 1; // boolean + case 'C': return 2; // char + case 'F': return 4; // float + case 'D': return 8; // double + case 'B': return 1; // byte + case 'S': return 2; // short + case 'I': return 4; // int + case 'J': return 8; // long + + case OBJECT: return mIdSize; + case BOOLEAN: return 1; + case CHAR: return 2; + case FLOAT: return 4; + case DOUBLE: return 8; + case BYTE: return 1; + case SHORT: return 2; + case INT: return 4; + case LONG: return 8; + } + + throw new IllegalArgumentException("Illegal type signature: " + type); + } + + public static final String getTypeName(int type) { + switch (type) { + case '[': return "array"; + case 'L': return "object"; + case 'Z': return "boolean"; + case 'C': return "char"; + case 'F': return "float"; + case 'D': return "double"; + case 'B': return "byte"; + case 'S': return "short"; + case 'I': return "int"; + case 'J': return "long"; + + case OBJECT: return "object"; + case BOOLEAN: return "boolean"; + case CHAR: return "char"; + case FLOAT: return "float"; + case DOUBLE: return "double"; + case BYTE: return "byte"; + case SHORT: return "short"; + case INT: return "int"; + case LONG: return "long"; + } + + throw new IllegalArgumentException("Illegal type signature: " + type); + } +} diff --git a/hit/test/testparser b/hit/test/testparser new file mode 100755 index 000000000..a3a722ed0 --- /dev/null +++ b/hit/test/testparser @@ -0,0 +1,8 @@ +#!/bin/sh + +find ../src -name \*java | xargs javac -d build -Xlint:unchecked + +# debug launch line tha turns off the jit and runs a debug server +#java -Djava.compiler=NONE -Xdebug -Xrunjdwp:transport=dt_socket,address=53635,server=y,suspend=y -cp build com.android.hit.Main ../samples/android.hprof + +java -cp build com.android.hit.Main ../samples/android.hprof |