Use a custom parser implementation instead of perflib.
Removes the dependency on perflib, avoids bugs in the perflib parser
whose fixes would not be readily available for ahat to use in the short
term, and avoids the need to pull in additional dependencies required to
use future versions of perflib.
Improves heap dump processing performance by 30-50% on some examples now
that we can avoid overheads due to the impedance mismatch between
perflib's object representation and ahat's object representation.
Other relevant changes included in this CL:
* Introduce enums for root types and basic types.
* Compute instance sizes on demand rather than eagerly.
* Introduce an abstraction for a collection of instances sorted by id.
Bug: 28312815
Bug: 33769446
Bug: 65356532
Test: m ahat-test, this change fixes all previously failing tests.
Change-Id: Ib14c294aa745cb03b02fa81d1544c78ff35e1edc
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
index 1d869af..ad6011d 100644
--- a/tools/ahat/Android.mk
+++ b/tools/ahat/Android.mk
@@ -24,7 +24,7 @@
LOCAL_JAR_MANIFEST := src/manifest.txt
LOCAL_JAVA_RESOURCE_FILES := \
$(LOCAL_PATH)/src/style.css
-LOCAL_STATIC_JAVA_LIBRARIES := perflib-prebuilt guavalib trove-prebuilt
+LOCAL_STATIC_JAVA_LIBRARIES := guavalib
LOCAL_IS_HOST_MODULE := true
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := ahat
diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt
index 4471c0a..ed40cb7 100644
--- a/tools/ahat/README.txt
+++ b/tools/ahat/README.txt
@@ -55,25 +55,6 @@
Reported Issues:
* Request to be able to sort tables by size.
-Perflib Requests:
- * Class objects should have java.lang.Class as their class object, not null.
- * ArrayInstance should have asString() to get the string, without requiring a
- length function.
- * Document that getHeapIndex returns -1 for no such heap.
- * Look up totalRetainedSize for a heap by Heap object, not by a separate heap
- index.
- * What's the difference between getId and getUniqueId?
- * I see objects with duplicate references.
- * A way to get overall retained size by heap.
- * A method Instance.isReachable()
-
-Things to move to perflib:
- * Extracting the string from a String Instance.
- * Extracting bitmap data from bitmap instances.
- * Adding up allocations by stack frame.
- * Computing, for each instance, the other instances it dominates.
- * Instance.isRoot and Instance.getRootTypes.
-
Release History:
1.4 Pending
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
index 7cda035..623a865 100644
--- a/tools/ahat/src/Main.java
+++ b/tools/ahat/src/Main.java
@@ -18,7 +18,8 @@
import com.android.ahat.heapdump.AhatSnapshot;
import com.android.ahat.heapdump.Diff;
-import com.android.tools.perflib.heap.ProguardMap;
+import com.android.ahat.heapdump.Parser;
+import com.android.ahat.proguard.ProguardMap;
import com.sun.net.httpserver.HttpServer;
import java.io.File;
import java.io.IOException;
@@ -46,7 +47,7 @@
out.println("");
}
- public static void main(String[] args) throws IOException {
+ public static void main(String[] args) throws Exception {
int port = 7100;
for (String arg : args) {
if (arg.equals("--help")) {
@@ -110,11 +111,11 @@
HttpServer server = HttpServer.create(addr, 0);
System.out.println("Processing hprof file...");
- AhatSnapshot ahat = AhatSnapshot.fromHprof(hprof, map);
+ AhatSnapshot ahat = Parser.parseHeapDump(hprof, map);
if (hprofbase != null) {
System.out.println("Processing baseline hprof file...");
- AhatSnapshot base = AhatSnapshot.fromHprof(hprofbase, mapbase);
+ AhatSnapshot base = Parser.parseHeapDump(hprofbase, mapbase);
System.out.println("Diffing hprof files...");
Diff.snapshots(ahat, base);
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index f4926aa..79f8b76 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -25,6 +25,7 @@
import com.android.ahat.heapdump.DiffedFieldValue;
import com.android.ahat.heapdump.FieldValue;
import com.android.ahat.heapdump.PathElement;
+import com.android.ahat.heapdump.RootType;
import com.android.ahat.heapdump.Site;
import com.android.ahat.heapdump.Value;
import java.io.IOException;
@@ -74,13 +75,13 @@
doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName()));
- Collection<String> rootTypes = inst.getRootTypes();
+ Collection<RootType> rootTypes = inst.getRootTypes();
if (rootTypes != null) {
DocString types = new DocString();
String comma = "";
- for (String type : rootTypes) {
+ for (RootType type : rootTypes) {
types.append(comma);
- types.append(type);
+ types.append(type.toString());
comma = ", ";
}
doc.description(DocString.text("Root Types"), types);
@@ -175,21 +176,21 @@
was.append(Summarizer.summarize(previous));
switch (field.status) {
case ADDED:
- doc.row(DocString.text(field.type),
+ doc.row(DocString.text(field.type.name),
DocString.text(field.name),
Summarizer.summarize(field.current),
DocString.added("new"));
break;
case MATCHED:
- doc.row(DocString.text(field.type),
+ doc.row(DocString.text(field.type.name),
DocString.text(field.name),
Summarizer.summarize(field.current),
Objects.equals(field.current, previous) ? new DocString() : was);
break;
case DELETED:
- doc.row(DocString.text(field.type),
+ doc.row(DocString.text(field.type.name),
DocString.text(field.name),
DocString.removed("del"),
was);
diff --git a/tools/ahat/src/heapdump/AhatArrayInstance.java b/tools/ahat/src/heapdump/AhatArrayInstance.java
index 8d23276..50a4805 100644
--- a/tools/ahat/src/heapdump/AhatArrayInstance.java
+++ b/tools/ahat/src/heapdump/AhatArrayInstance.java
@@ -16,20 +16,20 @@
package com.android.ahat.heapdump;
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.Instance;
import java.nio.charset.StandardCharsets;
import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
public class AhatArrayInstance extends AhatInstance {
- // To save space, we store byte, character, and object arrays directly as
- // byte, character, and AhatInstance arrays respectively. This is especially
- // important for large byte arrays, such as bitmaps. All other array types
- // are stored as an array of objects, though we could potentially save space
- // by specializing those too. mValues is a list view of the underlying
- // array.
+ // To save space, we store arrays as primitive arrays or AhatInstance arrays
+ // and provide a wrapper over the arrays to expose a list of Values.
+ // This is especially important for large byte arrays, such as bitmaps.
+ // We keep a separate pointer to the underlying array in the case of byte or
+ // char arrays because they are sometimes useful to have.
+ // TODO: Have different subtypes of AhatArrayInstance to avoid the overhead
+ // of these extra pointers and cost in getReferences when the array type is
+ // not relevant?
private List<Value> mValues;
private byte[] mByteArray; // null if not a byte array.
private char[] mCharArray; // null if not a char array.
@@ -38,72 +38,151 @@
super(id);
}
- @Override void initialize(AhatSnapshot snapshot, Instance inst, Site site) {
- super.initialize(snapshot, inst, site);
+ /**
+ * Initialize the array elements for a primitive boolean array.
+ */
+ void initialize(final boolean[] bools) {
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return bools.length;
+ }
- ArrayInstance array = (ArrayInstance)inst;
- switch (array.getArrayType()) {
- case OBJECT:
- Object[] objects = array.getValues();
- final AhatInstance[] insts = new AhatInstance[objects.length];
- for (int i = 0; i < objects.length; i++) {
- if (objects[i] != null) {
- Instance ref = (Instance)objects[i];
- insts[i] = snapshot.findInstance(ref.getId());
- }
- }
- mValues = new AbstractList<Value>() {
- @Override public int size() {
- return insts.length;
- }
+ @Override public Value get(int index) {
+ return Value.pack(bools[index]);
+ }
+ };
+ }
- @Override public Value get(int index) {
- return Value.pack(insts[index]);
- }
- };
- break;
+ /**
+ * Initialize the array elements for a primitive char array.
+ */
+ void initialize(final char[] chars) {
+ mCharArray = chars;
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return chars.length;
+ }
- case CHAR:
- final char[] chars = array.asCharArray(0, array.getLength());
- mCharArray = chars;
- mValues = new AbstractList<Value>() {
- @Override public int size() {
- return chars.length;
- }
+ @Override public Value get(int index) {
+ return Value.pack(chars[index]);
+ }
+ };
+ }
- @Override public Value get(int index) {
- return Value.pack(chars[index]);
- }
- };
- break;
+ /**
+ * Initialize the array elements for a primitive float array.
+ */
+ void initialize(final float[] floats) {
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return floats.length;
+ }
- case BYTE:
- final byte[] bytes = array.asRawByteArray(0, array.getLength());
- mByteArray = bytes;
- mValues = new AbstractList<Value>() {
- @Override public int size() {
- return bytes.length;
- }
+ @Override public Value get(int index) {
+ return Value.pack(floats[index]);
+ }
+ };
+ }
- @Override public Value get(int index) {
- return Value.pack(bytes[index]);
- }
- };
- break;
+ /**
+ * Initialize the array elements for a primitive double array.
+ */
+ void initialize(final double[] doubles) {
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return doubles.length;
+ }
- default:
- final Object[] values = array.getValues();
- mValues = new AbstractList<Value>() {
- @Override public int size() {
- return values.length;
- }
+ @Override public Value get(int index) {
+ return Value.pack(doubles[index]);
+ }
+ };
+ }
- @Override public Value get(int index) {
- return Value.pack(values[index]);
- }
- };
- break;
+ /**
+ * Initialize the array elements for a primitive byte array.
+ */
+ void initialize(final byte[] bytes) {
+ mByteArray = bytes;
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return bytes.length;
+ }
+
+ @Override public Value get(int index) {
+ return Value.pack(bytes[index]);
+ }
+ };
+ }
+
+ /**
+ * Initialize the array elements for a primitive short array.
+ */
+ void initialize(final short[] shorts) {
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return shorts.length;
+ }
+
+ @Override public Value get(int index) {
+ return Value.pack(shorts[index]);
+ }
+ };
+ }
+
+ /**
+ * Initialize the array elements for a primitive int array.
+ */
+ void initialize(final int[] ints) {
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return ints.length;
+ }
+
+ @Override public Value get(int index) {
+ return Value.pack(ints[index]);
+ }
+ };
+ }
+
+ /**
+ * Initialize the array elements for a primitive long array.
+ */
+ void initialize(final long[] longs) {
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return longs.length;
+ }
+
+ @Override public Value get(int index) {
+ return Value.pack(longs[index]);
+ }
+ };
+ }
+
+ /**
+ * Initialize the array elements for an instance array.
+ */
+ void initialize(final AhatInstance[] insts) {
+ mValues = new AbstractList<Value>() {
+ @Override public int size() {
+ return insts.length;
+ }
+
+ @Override public Value get(int index) {
+ return Value.pack(insts[index]);
+ }
+ };
+ }
+
+ @Override
+ protected long getExtraJavaSize() {
+ int length = getLength();
+ if (length == 0) {
+ return 0;
}
+
+ return Value.getType(mValues.get(0)).size * getLength();
}
/**
diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java
index 0224025..94efa50 100644
--- a/tools/ahat/src/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -16,11 +16,8 @@
package com.android.ahat.heapdump;
-import com.android.tools.perflib.heap.ClassInstance;
-import com.android.tools.perflib.heap.Instance;
import java.awt.image.BufferedImage;
import java.util.Iterator;
-import java.util.List;
import java.util.NoSuchElementException;
public class AhatClassInstance extends AhatInstance {
@@ -34,15 +31,13 @@
super(id);
}
- @Override void initialize(AhatSnapshot snapshot, Instance inst, Site site) {
- super.initialize(snapshot, inst, site);
+ void initialize(Value[] fields) {
+ mFields = fields;
+ }
- ClassInstance classInst = (ClassInstance)inst;
- List<ClassInstance.FieldValue> fieldValues = classInst.getValues();
- mFields = new Value[fieldValues.size()];
- for (int i = 0; i < mFields.length; i++) {
- mFields[i] = snapshot.getValue(fieldValues.get(i).getValue());
- }
+ @Override
+ protected long getExtraJavaSize() {
+ return 0;
}
@Override public Value getField(String fieldName) {
diff --git a/tools/ahat/src/heapdump/AhatClassObj.java b/tools/ahat/src/heapdump/AhatClassObj.java
index 08c7097..be0f713 100644
--- a/tools/ahat/src/heapdump/AhatClassObj.java
+++ b/tools/ahat/src/heapdump/AhatClassObj.java
@@ -16,13 +16,9 @@
package com.android.ahat.heapdump;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Instance;
import java.util.AbstractList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
-import java.util.Map;
public class AhatClassObj extends AhatInstance {
private String mClassName;
@@ -30,43 +26,32 @@
private AhatInstance mClassLoader;
private FieldValue[] mStaticFieldValues;
private Field[] mInstanceFields;
+ private long mStaticFieldsSize;
+ private long mInstanceSize;
- public AhatClassObj(long id) {
+ public AhatClassObj(long id, String className) {
super(id);
+ mClassName = className;
}
- @Override void initialize(AhatSnapshot snapshot, Instance inst, Site site) {
- super.initialize(snapshot, inst, site);
+ void initialize(AhatClassObj superClass,
+ long instanceSize,
+ Field[] instanceFields,
+ long staticFieldsSize) {
+ mSuperClassObj = superClass;
+ mInstanceSize = instanceSize;
+ mInstanceFields = instanceFields;
+ mStaticFieldsSize = staticFieldsSize;
+ }
- ClassObj classObj = (ClassObj)inst;
- mClassName = classObj.getClassName();
+ void initialize(AhatInstance classLoader, FieldValue[] staticFields) {
+ mClassLoader = classLoader;
+ mStaticFieldValues = staticFields;
+ }
- ClassObj superClassObj = classObj.getSuperClassObj();
- if (superClassObj != null) {
- mSuperClassObj = snapshot.findClassObj(superClassObj.getId());
- }
-
- Instance loader = classObj.getClassLoader();
- if (loader != null) {
- mClassLoader = snapshot.findInstance(loader.getId());
- }
-
- Collection<Map.Entry<com.android.tools.perflib.heap.Field, Object>> fieldValues
- = classObj.getStaticFieldValues().entrySet();
- mStaticFieldValues = new FieldValue[fieldValues.size()];
- int index = 0;
- for (Map.Entry<com.android.tools.perflib.heap.Field, Object> field : fieldValues) {
- String name = field.getKey().getName();
- String type = field.getKey().getType().toString();
- Value value = snapshot.getValue(field.getValue());
- mStaticFieldValues[index++] = new FieldValue(name, type, value);
- }
-
- com.android.tools.perflib.heap.Field[] fields = classObj.getFields();
- mInstanceFields = new Field[fields.length];
- for (int i = 0; i < fields.length; i++) {
- mInstanceFields[i] = new Field(fields[i].getName(), fields[i].getType().toString());
- }
+ @Override
+ protected long getExtraJavaSize() {
+ return mStaticFieldsSize;
}
/**
@@ -91,6 +76,14 @@
}
/**
+ * Returns the size of instances of this object, as reported in the heap
+ * dump.
+ */
+ public long getInstanceSize() {
+ return mInstanceSize;
+ }
+
+ /**
* Returns the static field values for this class object.
*/
public List<FieldValue> getStaticFieldValues() {
diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/heapdump/AhatInstance.java
index 05dcc68..c044487 100644
--- a/tools/ahat/src/heapdump/AhatInstance.java
+++ b/tools/ahat/src/heapdump/AhatInstance.java
@@ -17,8 +17,6 @@
package com.android.ahat.heapdump;
import com.android.ahat.dominators.DominatorsComputation;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Instance;
import java.awt.image.BufferedImage;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -34,14 +32,15 @@
private final long mId;
// Fields initialized in initialize().
- private Size mSize;
private AhatHeap mHeap;
private AhatClassObj mClassObj;
private Site mSite;
- // If this instance is a root, mRootTypes contains a set of the root types.
- // If this instance is not a root, mRootTypes is null.
- private List<String> mRootTypes;
+ // Bit vector of the root types of this object.
+ private int mRootTypes;
+
+ // Field initialized via addRegisterednativeSize.
+ private long mRegisteredNativeSize = 0;
// Fields initialized in computeReverseReferences().
private AhatInstance mNextInstanceToGcRoot;
@@ -55,33 +54,29 @@
private AhatInstance mImmediateDominator;
private List<AhatInstance> mDominated = new ArrayList<AhatInstance>();
private Size[] mRetainedSizes;
- private Object mDominatorsComputationState;
// The baseline instance for purposes of diff.
private AhatInstance mBaseline;
+ // temporary user data associated with this instance. This is used for a
+ // couple different purposes:
+ // 1. During parsing of instances, to store temporary field data.
+ // 2. During dominators computation, to store the dominators computation state.
+ private Object mTemporaryUserData;
+
public AhatInstance(long id) {
mId = id;
mBaseline = this;
}
/**
- * Initializes this AhatInstance based on the given perflib instance.
- * The AhatSnapshot should be used to look up AhatInstances and AhatHeaps.
- * There is no guarantee that the AhatInstances returned by
- * snapshot.findInstance have been initialized yet.
+ * Initialize this AhatInstance based on the the given info.
*/
- void initialize(AhatSnapshot snapshot, Instance inst, Site site) {
- site.addInstance(this);
- mSize = new Size(inst.getSize(), 0);
- mHeap = snapshot.getHeap(inst.getHeap().getName());
-
- ClassObj clsObj = inst.getClassObj();
- if (clsObj != null) {
- mClassObj = snapshot.findClassObj(clsObj.getId());
- }
-
+ void initialize(AhatHeap heap, Site site, AhatClassObj classObj) {
+ mHeap = heap;
mSite = site;
+ site.addInstance(this);
+ mClassObj = classObj;
}
/**
@@ -95,10 +90,20 @@
* Returns the shallow number of bytes this object takes up.
*/
public Size getSize() {
- return mSize;
+ return new Size(mClassObj.getInstanceSize() + getExtraJavaSize(), mRegisteredNativeSize);
}
/**
+ * Returns the number of bytes taken up by this object on the Java heap
+ * beyond the standard instance size as recorded by the class of this
+ * instance.
+ *
+ * For example, class objects will have extra size for static fields and
+ * array objects will have extra size for the array elements.
+ */
+ protected abstract long getExtraJavaSize();
+
+ /**
* Returns the number of bytes belonging to the given heap that this instance
* retains.
*/
@@ -127,7 +132,7 @@
* Increment the number of registered native bytes tied to this object.
*/
void addRegisteredNativeSize(long size) {
- mSize = mSize.plusRegisteredNativeSize(size);
+ mRegisteredNativeSize += size;
}
/**
@@ -154,27 +159,32 @@
* Returns true if this instance is marked as a root instance.
*/
public boolean isRoot() {
- return mRootTypes != null;
+ return mRootTypes != 0;
}
/**
* Marks this instance as being a root of the given type.
*/
- void addRootType(String type) {
- if (mRootTypes == null) {
- mRootTypes = new ArrayList<String>();
- mRootTypes.add(type);
- } else if (!mRootTypes.contains(type)) {
- mRootTypes.add(type);
- }
+ void addRootType(RootType type) {
+ mRootTypes |= type.mask;
}
/**
- * Returns a list of string descriptions of the root types of this object.
+ * Returns a list of the root types of this object.
* Returns null if this object is not a root.
*/
- public Collection<String> getRootTypes() {
- return mRootTypes;
+ public Collection<RootType> getRootTypes() {
+ if (!isRoot()) {
+ return null;
+ }
+
+ List<RootType> types = new ArrayList<RootType>();
+ for (RootType type : RootType.values()) {
+ if ((mRootTypes & type.mask) != 0) {
+ types.add(type);
+ }
+ }
+ return types;
}
/**
@@ -446,6 +456,14 @@
return new AhatPlaceHolderInstance(this);
}
+ public void setTemporaryUserData(Object state) {
+ mTemporaryUserData = state;
+ }
+
+ public Object getTemporaryUserData() {
+ return mTemporaryUserData;
+ }
+
/**
* Initialize the reverse reference fields of this instance and all other
* instances reachable from it. Initializes the following fields:
@@ -511,7 +529,7 @@
}
if (!(inst instanceof SuperRoot)) {
inst.mRetainedSizes[inst.mHeap.getIndex()] =
- inst.mRetainedSizes[inst.mHeap.getIndex()].plus(inst.mSize);
+ inst.mRetainedSizes[inst.mHeap.getIndex()].plus(inst.getSize());
}
deque.push(inst);
for (AhatInstance dominated : inst.mDominated) {
@@ -529,12 +547,12 @@
@Override
public void setDominatorsComputationState(Object state) {
- mDominatorsComputationState = state;
+ setTemporaryUserData(state);
}
@Override
public Object getDominatorsComputationState() {
- return mDominatorsComputationState;
+ return getTemporaryUserData();
}
@Override
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
index d7f94dc..07f5b50 100644
--- a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
@@ -24,7 +24,7 @@
*/
public class AhatPlaceHolderClassObj extends AhatClassObj {
AhatPlaceHolderClassObj(AhatClassObj baseline) {
- super(-1);
+ super(-1, baseline.getClassName());
setBaseline(baseline);
baseline.setBaseline(this);
}
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
index 9abc952..8849403 100644
--- a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
@@ -36,6 +36,10 @@
return Size.ZERO;
}
+ @Override protected long getExtraJavaSize() {
+ return 0;
+ }
+
@Override public Size getRetainedSize(AhatHeap heap) {
return Size.ZERO;
}
diff --git a/tools/ahat/src/heapdump/AhatSnapshot.java b/tools/ahat/src/heapdump/AhatSnapshot.java
index 703ba25..945966c 100644
--- a/tools/ahat/src/heapdump/AhatSnapshot.java
+++ b/tools/ahat/src/heapdump/AhatSnapshot.java
@@ -17,164 +17,43 @@
package com.android.ahat.heapdump;
import com.android.ahat.dominators.DominatorsComputation;
-import com.android.tools.perflib.captures.DataBuffer;
-import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.ClassInstance;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.ProguardMap;
-import com.android.tools.perflib.heap.RootObj;
-import com.android.tools.perflib.heap.Snapshot;
-import com.android.tools.perflib.heap.StackFrame;
-import com.android.tools.perflib.heap.StackTrace;
-import gnu.trove.TObjectProcedure;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Comparator;
import java.util.List;
public class AhatSnapshot implements Diffable<AhatSnapshot> {
- private final Site mRootSite = new Site("ROOT");
+ private final Site mRootSite;
- // Collection of objects whose immediate dominator is the SENTINEL_ROOT.
- private final List<AhatInstance> mRooted;
+ private final SuperRoot mSuperRoot;
- // List of all ahat instances stored in increasing order by id.
- private final List<AhatInstance> mInstances = new ArrayList<AhatInstance>();
+ // List of all ahat instances.
+ private final Instances<AhatInstance> mInstances;
- private final List<AhatHeap> mHeaps = new ArrayList<AhatHeap>();
+ private List<AhatHeap> mHeaps;
private AhatSnapshot mBaseline = this;
- /**
- * Create an AhatSnapshot from an hprof file.
- */
- public static AhatSnapshot fromHprof(File hprof, ProguardMap map) throws IOException {
- return fromDataBuffer(new MemoryMappedFileBuffer(hprof), map);
- }
-
- /**
- * Create an AhatSnapshot from an in-memory data buffer.
- */
- public static AhatSnapshot fromDataBuffer(DataBuffer buffer, ProguardMap map) throws IOException {
- AhatSnapshot snapshot = new AhatSnapshot(buffer, map);
-
- // Request a GC now to clean up memory used by perflib. This helps to
- // avoid a noticable pause when visiting the first interesting page in
- // ahat.
- System.gc();
-
- return snapshot;
- }
-
- /**
- * Constructs an AhatSnapshot for the given hprof binary data.
- */
- private AhatSnapshot(DataBuffer buffer, ProguardMap map) throws IOException {
- Snapshot snapshot = Snapshot.createSnapshot(buffer, map);
-
- // Properly label the class of class objects in the perflib snapshot.
- final ClassObj javaLangClass = snapshot.findClass("java.lang.Class");
- if (javaLangClass != null) {
- for (Heap heap : snapshot.getHeaps()) {
- Collection<ClassObj> classes = heap.getClasses();
- for (ClassObj clsObj : classes) {
- if (clsObj.getClassObj() == null) {
- clsObj.setClassId(javaLangClass.getId());
- }
- }
- }
- }
-
- // Create mappings from id to ahat instance and heaps.
- Collection<Heap> heaps = snapshot.getHeaps();
- for (Heap heap : heaps) {
- // Note: mHeaps will not be in index order if snapshot.getHeaps does not
- // return heaps in index order. That's fine, because we don't rely on
- // mHeaps being in index order.
- mHeaps.add(new AhatHeap(heap.getName(), snapshot.getHeapIndex(heap)));
- TObjectProcedure<Instance> doCreate = new TObjectProcedure<Instance>() {
- @Override
- public boolean execute(Instance inst) {
- long id = inst.getId();
- if (inst instanceof ClassInstance) {
- mInstances.add(new AhatClassInstance(id));
- } else if (inst instanceof ArrayInstance) {
- mInstances.add(new AhatArrayInstance(id));
- } else if (inst instanceof ClassObj) {
- AhatClassObj classObj = new AhatClassObj(id);
- mInstances.add(classObj);
- }
- return true;
- }
- };
- for (Instance instance : heap.getClasses()) {
- doCreate.execute(instance);
- }
- heap.forEachInstance(doCreate);
- }
-
- // Sort the instances by id so we can use binary search to lookup
- // instances by id.
- mInstances.sort(new Comparator<AhatInstance>() {
- @Override
- public int compare(AhatInstance a, AhatInstance b) {
- return Long.compare(a.getId(), b.getId());
- }
- });
-
- // Initialize ahat snapshot and instances based on the perflib snapshot
- // and instances.
- List<AhatInstance> cleaners = new ArrayList<AhatInstance>();
- for (AhatInstance ahat : mInstances) {
- Instance inst = snapshot.findInstance(ahat.getId());
-
- StackFrame[] frames = null;
- StackTrace stack = inst.getStack();
- if (stack != null) {
- frames = stack.getFrames();
- }
- ahat.initialize(this, inst, mRootSite.getSite(frames));
-
- ClassObj classObj = inst.getClassObj();
- if (classObj != null && "sun.misc.Cleaner".equals(classObj.getClassName())) {
- cleaners.add(ahat);
- }
- }
-
- // Record the roots and their types.
- SuperRoot superRoot = new SuperRoot();
- for (RootObj root : snapshot.getGCRoots()) {
- Instance inst = root.getReferredInstance();
- if (inst != null) {
- AhatInstance ahat = findInstance(inst.getId());
- if (!ahat.isRoot()) {
- superRoot.addRoot(ahat);
- }
- ahat.addRootType(root.getRootType().toString());
- }
- }
- snapshot.dispose();
+ AhatSnapshot(SuperRoot root,
+ Instances<AhatInstance> instances,
+ List<AhatHeap> heaps,
+ Site rootSite) {
+ mSuperRoot = root;
+ mInstances = instances;
+ mHeaps = heaps;
+ mRootSite = rootSite;
// Update registered native allocation size.
- for (AhatInstance cleaner : cleaners) {
+ for (AhatInstance cleaner : mInstances) {
AhatInstance.RegisteredNativeAllocation nra = cleaner.asRegisteredNativeAllocation();
if (nra != null) {
nra.referent.addRegisteredNativeSize(nra.size);
}
}
- AhatInstance.computeReverseReferences(superRoot);
- DominatorsComputation.computeDominators(superRoot);
- AhatInstance.computeRetainedSize(superRoot, mHeaps.size());
+ AhatInstance.computeReverseReferences(mSuperRoot);
+ DominatorsComputation.computeDominators(mSuperRoot);
+ AhatInstance.computeRetainedSize(mSuperRoot, mHeaps.size());
- mRooted = superRoot.getDominated();
for (AhatHeap heap : mHeaps) {
- heap.addToSize(superRoot.getRetainedSize(heap));
+ heap.addToSize(mSuperRoot.getRetainedSize(heap));
}
mRootSite.prepareForUse(0, mHeaps.size());
@@ -185,22 +64,7 @@
* Returns null if no instance with the given id is found.
*/
public AhatInstance findInstance(long id) {
- // Binary search over the sorted instances.
- int start = 0;
- int end = mInstances.size();
- while (start < end) {
- int mid = start + ((end - start) / 2);
- AhatInstance midInst = mInstances.get(mid);
- long midId = midInst.getId();
- if (id == midId) {
- return midInst;
- } else if (id < midId) {
- end = mid;
- } else {
- start = mid + 1;
- }
- }
- return null;
+ return mInstances.get(id);
}
/**
@@ -241,7 +105,7 @@
* SENTINEL_ROOT.
*/
public List<AhatInstance> getRooted() {
- return mRooted;
+ return mSuperRoot.getDominated();
}
/**
@@ -258,14 +122,6 @@
return site == null ? mRootSite : site;
}
- // Return the Value for the given perflib value object.
- Value getValue(Object value) {
- if (value instanceof Instance) {
- value = findInstance(((Instance)value).getId());
- }
- return Value.pack(value);
- }
-
public void setBaseline(AhatSnapshot baseline) {
mBaseline = baseline;
}
diff --git a/tools/ahat/src/heapdump/DiffedFieldValue.java b/tools/ahat/src/heapdump/DiffedFieldValue.java
index e2dcf3e..3cd273e 100644
--- a/tools/ahat/src/heapdump/DiffedFieldValue.java
+++ b/tools/ahat/src/heapdump/DiffedFieldValue.java
@@ -23,7 +23,7 @@
*/
public class DiffedFieldValue {
public final String name;
- public final String type;
+ public final Type type;
public final Value current;
public final Value baseline;
@@ -60,7 +60,7 @@
return new DiffedFieldValue(baseline.name, baseline.type, null, baseline.value, Status.DELETED);
}
- private DiffedFieldValue(String name, String type, Value current, Value baseline, Status status) {
+ private DiffedFieldValue(String name, Type type, Value current, Value baseline, Status status) {
this.name = name;
this.type = type;
this.current = current;
diff --git a/tools/ahat/src/heapdump/Field.java b/tools/ahat/src/heapdump/Field.java
index 01f87c7..dff4017 100644
--- a/tools/ahat/src/heapdump/Field.java
+++ b/tools/ahat/src/heapdump/Field.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,9 @@
public class Field {
public final String name;
- public final String type;
+ public final Type type;
- public Field(String name, String type) {
+ public Field(String name, Type type) {
this.name = name;
this.type = type;
}
diff --git a/tools/ahat/src/heapdump/FieldValue.java b/tools/ahat/src/heapdump/FieldValue.java
index 6d72595..20e6da7 100644
--- a/tools/ahat/src/heapdump/FieldValue.java
+++ b/tools/ahat/src/heapdump/FieldValue.java
@@ -18,10 +18,10 @@
public class FieldValue {
public final String name;
- public final String type;
+ public final Type type;
public final Value value;
- public FieldValue(String name, String type, Value value) {
+ public FieldValue(String name, Type type, Value value) {
this.name = name;
this.type = type;
this.value = value;
diff --git a/tools/ahat/src/heapdump/HprofFormatException.java b/tools/ahat/src/heapdump/HprofFormatException.java
new file mode 100644
index 0000000..55e8958
--- /dev/null
+++ b/tools/ahat/src/heapdump/HprofFormatException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+public class HprofFormatException extends Exception {
+ public HprofFormatException(String msg) {
+ super(msg);
+ }
+}
diff --git a/tools/ahat/src/heapdump/Instances.java b/tools/ahat/src/heapdump/Instances.java
new file mode 100644
index 0000000..0851446
--- /dev/null
+++ b/tools/ahat/src/heapdump/Instances.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A collection of instances that can be searched for by id.
+ */
+class Instances<T extends AhatInstance> implements Iterable<T> {
+
+ private final List<T> mInstances;
+
+ /**
+ * Create a collection of instances that can be looked up by id.
+ * Note: this takes ownership of the given list of instances.
+ */
+ public Instances(List<T> instances) {
+ mInstances = instances;
+
+ // Sort the instances by id so we can use binary search to lookup
+ // instances by id.
+ instances.sort(new Comparator<AhatInstance>() {
+ @Override
+ public int compare(AhatInstance a, AhatInstance b) {
+ return Long.compare(a.getId(), b.getId());
+ }
+ });
+ }
+
+ /**
+ * Look up an instance by id.
+ * Returns null if no instance with the given id is found.
+ */
+ public T get(long id) {
+ // Binary search over the sorted instances.
+ int start = 0;
+ int end = mInstances.size();
+ while (start < end) {
+ int mid = start + ((end - start) / 2);
+ T midInst = mInstances.get(mid);
+ long midId = midInst.getId();
+ if (id == midId) {
+ return midInst;
+ } else if (id < midId) {
+ end = mid;
+ } else {
+ start = mid + 1;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return mInstances.iterator();
+ }
+}
+
diff --git a/tools/ahat/src/heapdump/Parser.java b/tools/ahat/src/heapdump/Parser.java
new file mode 100644
index 0000000..3d5f95f
--- /dev/null
+++ b/tools/ahat/src/heapdump/Parser.java
@@ -0,0 +1,942 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.ahat.proguard.ProguardMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class Parser {
+ private static final int ID_SIZE = 4;
+
+ /**
+ * Parse the given heap dump using the given proguard map for deobfuscation.
+ * We make the following assumptions about valid heap dumps:
+ * Class serial numbers, stack frames, and stack traces
+ * individually satisfy the following:
+ * - all elements are defined before they are referenced.
+ * - ids are densely packed in some range [a, b] where a is not
+ * necessarily 0.
+ * - there are not more than 2^31 elements defined.
+ * All classes are defined via a LOAD CLASS record before the first heap
+ * dump segment.
+ * The ID size used in the heap dump is 4 bytes.
+ */
+ public static AhatSnapshot parseHeapDump(File hprof, ProguardMap map)
+ throws IOException, HprofFormatException {
+ return parseHeapDump(new HprofBuffer(hprof), map);
+ }
+
+ /**
+ * Parse a heap dump from a byte buffer.
+ */
+ public static AhatSnapshot parseHeapDump(ByteBuffer hprof, ProguardMap map)
+ throws IOException, HprofFormatException {
+ return parseHeapDump(new HprofBuffer(hprof), map);
+ }
+
+ private static AhatSnapshot parseHeapDump(HprofBuffer hprof, ProguardMap map)
+ throws IOException, HprofFormatException {
+ // Read, and mostly ignore, the hprof header info.
+ {
+ StringBuilder format = new StringBuilder();
+ int b;
+ while ((b = hprof.getU1()) != 0) {
+ format.append((char)b);
+ }
+
+ int idSize = hprof.getU4();
+ if (idSize != ID_SIZE) {
+ throw new HprofFormatException("Id size " + idSize + " not supported.");
+ }
+ int hightime = hprof.getU4();
+ int lowtime = hprof.getU4();
+ }
+
+ // First pass: Read through all the heap dump records. Construct the
+ // AhatInstances, initialize them as much as possible and save any
+ // additional temporary data we need to complete their initialization in
+ // the fixup pass.
+ Site rootSite = new Site("ROOT");
+ List<AhatInstance> instances = new ArrayList<AhatInstance>();
+ List<RootData> roots = new ArrayList<RootData>();
+ HeapList heaps = new HeapList();
+ {
+ // Note: Strings do not satisfy the DenseMap requirements on heap dumps
+ // from Android K.
+ UnDenseMap<String> strings = new UnDenseMap<String>("String");
+ DenseMap<ProguardMap.Frame> frames = new DenseMap<ProguardMap.Frame>("Stack Frame");
+ DenseMap<Site> sites = new DenseMap<Site>("Stack Trace");
+ DenseMap<String> classNamesBySerial = new DenseMap<String>("Class Serial Number");
+ AhatClassObj javaLangClass = null;
+ AhatClassObj[] primArrayClasses = new AhatClassObj[Type.values().length];
+ ArrayList<AhatClassObj> classes = new ArrayList<AhatClassObj>();
+ Instances<AhatClassObj> classById = null;
+
+ while (hprof.hasRemaining()) {
+ int tag = hprof.getU1();
+ int time = hprof.getU4();
+ int recordLength = hprof.getU4();
+ switch (tag) {
+ case 0x01: { // STRING
+ long id = hprof.getId();
+ byte[] bytes = new byte[recordLength - ID_SIZE];
+ hprof.getBytes(bytes);
+ String str = new String(bytes, StandardCharsets.UTF_8);
+ strings.put(id, str);
+ break;
+ }
+
+ case 0x02: { // LOAD CLASS
+ int classSerialNumber = hprof.getU4();
+ long objectId = hprof.getId();
+ int stackSerialNumber = hprof.getU4();
+ long classNameStringId = hprof.getId();
+ String obfClassName = strings.get(classNameStringId);
+ String clrClassName = map.getClassName(obfClassName);
+ AhatClassObj classObj = new AhatClassObj(objectId, clrClassName);
+ classNamesBySerial.put(classSerialNumber, clrClassName);
+ classes.add(classObj);
+
+ // Check whether this class is one of the special classes we are
+ // interested in, and if so, save it for later use.
+ if ("java.lang.Class".equals(clrClassName)) {
+ javaLangClass = classObj;
+ }
+
+ for (Type type : Type.values()) {
+ if (clrClassName.equals(type.name + "[]")) {
+ primArrayClasses[type.ordinal()] = classObj;
+ }
+ }
+ break;
+ }
+
+ case 0x04: { // STACK FRAME
+ long frameId = hprof.getId();
+ long methodNameStringId = hprof.getId();
+ long methodSignatureStringId = hprof.getId();
+ long methodFileNameStringId = hprof.getId();
+ int classSerialNumber = hprof.getU4();
+ int lineNumber = hprof.getU4();
+
+ ProguardMap.Frame frame = map.getFrame(
+ classNamesBySerial.get(classSerialNumber),
+ strings.get(methodNameStringId),
+ strings.get(methodSignatureStringId),
+ strings.get(methodFileNameStringId),
+ lineNumber);
+ frames.put(frameId, frame);
+ break;
+ }
+
+ case 0x05: { // STACK TRACE
+ int stackSerialNumber = hprof.getU4();
+ int threadSerialNumber = hprof.getU4();
+ int numFrames = hprof.getU4();
+ ProguardMap.Frame[] trace = new ProguardMap.Frame[numFrames];
+ for (int i = 0; i < numFrames; i++) {
+ long frameId = hprof.getId();
+ trace[i] = frames.get(frameId);
+ }
+ sites.put(stackSerialNumber, rootSite.getSite(trace));
+ break;
+ }
+
+ case 0x1C: { // HEAP DUMP SEGMENT
+ if (classById == null) {
+ classById = new Instances<AhatClassObj>(classes);
+ }
+ int subtag;
+ while (!isEndOfHeapDumpSegment(subtag = hprof.getU1())) {
+ switch (subtag) {
+ case 0x01: { // ROOT JNI GLOBAL
+ long objectId = hprof.getId();
+ long refId = hprof.getId();
+ roots.add(new RootData(objectId, RootType.JNI_GLOBAL));
+ break;
+ }
+
+ case 0x02: { // ROOT JNI LOCAL
+ long objectId = hprof.getId();
+ int threadSerialNumber = hprof.getU4();
+ int frameNumber = hprof.getU4();
+ roots.add(new RootData(objectId, RootType.JNI_LOCAL));
+ break;
+ }
+
+ case 0x03: { // ROOT JAVA FRAME
+ long objectId = hprof.getId();
+ int threadSerialNumber = hprof.getU4();
+ int frameNumber = hprof.getU4();
+ roots.add(new RootData(objectId, RootType.JAVA_FRAME));
+ break;
+ }
+
+ case 0x04: { // ROOT NATIVE STACK
+ long objectId = hprof.getId();
+ int threadSerialNumber = hprof.getU4();
+ roots.add(new RootData(objectId, RootType.NATIVE_STACK));
+ break;
+ }
+
+ case 0x05: { // ROOT STICKY CLASS
+ long objectId = hprof.getId();
+ roots.add(new RootData(objectId, RootType.STICKY_CLASS));
+ break;
+ }
+
+ case 0x06: { // ROOT THREAD BLOCK
+ long objectId = hprof.getId();
+ int threadSerialNumber = hprof.getU4();
+ roots.add(new RootData(objectId, RootType.THREAD_BLOCK));
+ break;
+ }
+
+ case 0x07: { // ROOT MONITOR USED
+ long objectId = hprof.getId();
+ roots.add(new RootData(objectId, RootType.MONITOR));
+ break;
+ }
+
+ case 0x08: { // ROOT THREAD OBJECT
+ long objectId = hprof.getId();
+ int threadSerialNumber = hprof.getU4();
+ int stackSerialNumber = hprof.getU4();
+ roots.add(new RootData(objectId, RootType.THREAD));
+ break;
+ }
+
+ case 0x20: { // CLASS DUMP
+ ClassObjData data = new ClassObjData();
+ long objectId = hprof.getId();
+ int stackSerialNumber = hprof.getU4();
+ long superClassId = hprof.getId();
+ data.classLoaderId = hprof.getId();
+ long signersId = hprof.getId();
+ long protectionId = hprof.getId();
+ long reserved1 = hprof.getId();
+ long reserved2 = hprof.getId();
+ int instanceSize = hprof.getU4();
+ int constantPoolSize = hprof.getU2();
+ for (int i = 0; i < constantPoolSize; ++i) {
+ int index = hprof.getU2();
+ Type type = hprof.getType();
+ hprof.skip(type.size);
+ }
+ int numStaticFields = hprof.getU2();
+ data.staticFields = new FieldValue[numStaticFields];
+ AhatClassObj obj = classById.get(objectId);
+ String clrClassName = obj.getName();
+ long staticFieldsSize = 0;
+ for (int i = 0; i < numStaticFields; ++i) {
+ String obfName = strings.get(hprof.getId());
+ String clrName = map.getFieldName(clrClassName, obfName);
+ Type type = hprof.getType();
+ Value value = hprof.getDeferredValue(type);
+ staticFieldsSize += type.size;
+ data.staticFields[i] = new FieldValue(clrName, type, value);
+ }
+ AhatClassObj superClass = classById.get(superClassId);
+ int numInstanceFields = hprof.getU2();
+ Field[] ifields = new Field[numInstanceFields];
+ for (int i = 0; i < numInstanceFields; ++i) {
+ String name = map.getFieldName(obj.getName(), strings.get(hprof.getId()));
+ ifields[i] = new Field(name, hprof.getType());
+ }
+ Site site = sites.get(stackSerialNumber);
+
+ if (javaLangClass == null) {
+ throw new HprofFormatException("No class definition found for java.lang.Class");
+ }
+ obj.initialize(heaps.getCurrentHeap(), site, javaLangClass);
+ obj.initialize(superClass, instanceSize, ifields, staticFieldsSize);
+ obj.setTemporaryUserData(data);
+ break;
+ }
+
+ case 0x21: { // INSTANCE DUMP
+ long objectId = hprof.getId();
+ int stackSerialNumber = hprof.getU4();
+ long classId = hprof.getId();
+ int numBytes = hprof.getU4();
+ ClassInstData data = new ClassInstData(hprof.tell());
+ hprof.skip(numBytes);
+
+ Site site = sites.get(stackSerialNumber);
+ AhatClassObj classObj = classById.get(classId);
+ AhatClassInstance obj = new AhatClassInstance(objectId);
+ obj.initialize(heaps.getCurrentHeap(), site, classObj);
+ obj.setTemporaryUserData(data);
+ instances.add(obj);
+ break;
+ }
+
+ case 0x22: { // OBJECT ARRAY DUMP
+ long objectId = hprof.getId();
+ int stackSerialNumber = hprof.getU4();
+ int length = hprof.getU4();
+ long classId = hprof.getId();
+ ObjArrayData data = new ObjArrayData(length, hprof.tell());
+ hprof.skip(length * ID_SIZE);
+
+ Site site = sites.get(stackSerialNumber);
+ AhatClassObj classObj = classById.get(classId);
+ AhatArrayInstance obj = new AhatArrayInstance(objectId);
+ obj.initialize(heaps.getCurrentHeap(), site, classObj);
+ obj.setTemporaryUserData(data);
+ instances.add(obj);
+ break;
+ }
+
+ case 0x23: { // PRIMITIVE ARRAY DUMP
+ long objectId = hprof.getId();
+ int stackSerialNumber = hprof.getU4();
+ int length = hprof.getU4();
+ Type type = hprof.getPrimitiveType();
+ Site site = sites.get(stackSerialNumber);
+
+ AhatClassObj classObj = primArrayClasses[type.ordinal()];
+ if (classObj == null) {
+ throw new HprofFormatException(
+ "No class definition found for " + type.name + "[]");
+ }
+
+ AhatArrayInstance obj = new AhatArrayInstance(objectId);
+ obj.initialize(heaps.getCurrentHeap(), site, classObj);
+ instances.add(obj);
+ switch (type) {
+ case BOOLEAN: {
+ boolean[] data = new boolean[length];
+ for (int i = 0; i < length; ++i) {
+ data[i] = hprof.getBool();
+ }
+ obj.initialize(data);
+ break;
+ }
+
+ case CHAR: {
+ char[] data = new char[length];
+ for (int i = 0; i < length; ++i) {
+ data[i] = hprof.getChar();
+ }
+ obj.initialize(data);
+ break;
+ }
+
+ case FLOAT: {
+ float[] data = new float[length];
+ for (int i = 0; i < length; ++i) {
+ data[i] = hprof.getFloat();
+ }
+ obj.initialize(data);
+ break;
+ }
+
+ case DOUBLE: {
+ double[] data = new double[length];
+ for (int i = 0; i < length; ++i) {
+ data[i] = hprof.getDouble();
+ }
+ obj.initialize(data);
+ break;
+ }
+
+ case BYTE: {
+ byte[] data = new byte[length];
+ hprof.getBytes(data);
+ obj.initialize(data);
+ break;
+ }
+
+ case SHORT: {
+ short[] data = new short[length];
+ for (int i = 0; i < length; ++i) {
+ data[i] = hprof.getShort();
+ }
+ obj.initialize(data);
+ break;
+ }
+
+ case INT: {
+ int[] data = new int[length];
+ for (int i = 0; i < length; ++i) {
+ data[i] = hprof.getInt();
+ }
+ obj.initialize(data);
+ break;
+ }
+
+ case LONG: {
+ long[] data = new long[length];
+ for (int i = 0; i < length; ++i) {
+ data[i] = hprof.getLong();
+ }
+ obj.initialize(data);
+ break;
+ }
+ }
+ break;
+ }
+
+ case 0x89: { // ROOT INTERNED STRING (ANDROID)
+ long objectId = hprof.getId();
+ roots.add(new RootData(objectId, RootType.INTERNED_STRING));
+ break;
+ }
+
+ case 0x8b: { // ROOT DEBUGGER (ANDROID)
+ long objectId = hprof.getId();
+ roots.add(new RootData(objectId, RootType.DEBUGGER));
+ break;
+ }
+
+ case 0x8d: { // ROOT VM INTERNAL (ANDROID)
+ long objectId = hprof.getId();
+ roots.add(new RootData(objectId, RootType.VM_INTERNAL));
+ break;
+ }
+
+ case 0x8e: { // ROOT JNI MONITOR (ANDROID)
+ long objectId = hprof.getId();
+ int threadSerialNumber = hprof.getU4();
+ int frameNumber = hprof.getU4();
+ roots.add(new RootData(objectId, RootType.JNI_MONITOR));
+ break;
+ }
+
+ case 0xfe: { // HEAP DUMP INFO (ANDROID)
+ int type = hprof.getU4();
+ long stringId = hprof.getId();
+ heaps.setCurrentHeap(strings.get(stringId));
+ break;
+ }
+
+ case 0xff: { // ROOT UNKNOWN
+ long objectId = hprof.getId();
+ roots.add(new RootData(objectId, RootType.UNKNOWN));
+ break;
+ }
+
+ default:
+ throw new HprofFormatException(
+ String.format("Unsupported heap dump sub tag 0x%02x", subtag));
+ }
+ }
+
+ // Reset the file pointer back because we read the first byte into
+ // the next record.
+ hprof.skip(-1);
+ break;
+ }
+
+ default:
+ // Ignore any other tags that we either don't know about or don't
+ // care about.
+ hprof.skip(recordLength);
+ break;
+ }
+ }
+
+ instances.addAll(classes);
+ }
+
+ // Sort roots and instances by id in preparation for the fixup pass.
+ Instances<AhatInstance> mInstances = new Instances<AhatInstance>(instances);
+ roots.sort(new Comparator<RootData>() {
+ @Override
+ public int compare(RootData a, RootData b) {
+ return Long.compare(a.id, b.id);
+ }
+ });
+ roots.add(null);
+
+ // Fixup pass: Label the root instances and fix up references to instances
+ // that we couldn't previously resolve.
+ SuperRoot superRoot = new SuperRoot();
+ {
+ Iterator<RootData> ri = roots.iterator();
+ RootData root = ri.next();
+ for (AhatInstance inst : mInstances) {
+ long id = inst.getId();
+
+ // Skip past any roots that don't have associated instances.
+ // It's not clear why there would be a root without an associated
+ // instance dump, but it does happen in practice, for example when
+ // taking heap dumps using the RI.
+ while (root != null && root.id < id) {
+ root = ri.next();
+ }
+
+ // Check if this instance is a root, and if so, update its root types.
+ if (root != null && root.id == id) {
+ superRoot.addRoot(inst);
+ while (root != null && root.id == id) {
+ inst.addRootType(root.type);
+ root = ri.next();
+ }
+ }
+
+ // Fixup the instance based on its type using the temporary data we
+ // saved during the first pass over the heap dump.
+ if (inst instanceof AhatClassInstance) {
+ ClassInstData data = (ClassInstData)inst.getTemporaryUserData();
+ inst.setTemporaryUserData(null);
+
+ // Compute the size of the fields array in advance to avoid
+ // extra allocations and copies that would come from using an array
+ // list to collect the field values.
+ int numFields = 0;
+ for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) {
+ numFields += cls.getInstanceFields().length;
+ }
+
+ Value[] fields = new Value[numFields];
+ int i = 0;
+ hprof.seek(data.position);
+ for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) {
+ for (Field field : cls.getInstanceFields()) {
+ fields[i++] = hprof.getValue(field.type, mInstances);
+ }
+ }
+ ((AhatClassInstance)inst).initialize(fields);
+ } else if (inst instanceof AhatClassObj) {
+ ClassObjData data = (ClassObjData)inst.getTemporaryUserData();
+ inst.setTemporaryUserData(null);
+ AhatInstance loader = mInstances.get(data.classLoaderId);
+ for (int i = 0; i < data.staticFields.length; ++i) {
+ FieldValue field = data.staticFields[i];
+ if (field.value instanceof DeferredInstanceValue) {
+ DeferredInstanceValue deferred = (DeferredInstanceValue)field.value;
+ data.staticFields[i] = new FieldValue(
+ field.name, field.type, Value.pack(mInstances.get(deferred.getId())));
+ }
+ }
+ ((AhatClassObj)inst).initialize(loader, data.staticFields);
+ } else if (inst instanceof AhatArrayInstance && inst.getTemporaryUserData() != null) {
+ // TODO: Have specialized object array instance and check for that
+ // rather than checking for the presence of user data?
+ ObjArrayData data = (ObjArrayData)inst.getTemporaryUserData();
+ inst.setTemporaryUserData(null);
+ AhatInstance[] array = new AhatInstance[data.length];
+ hprof.seek(data.position);
+ for (int i = 0; i < data.length; i++) {
+ array[i] = mInstances.get(hprof.getId());
+ }
+ ((AhatArrayInstance)inst).initialize(array);
+ }
+ }
+ }
+
+ hprof = null;
+ roots = null;
+ return new AhatSnapshot(superRoot, mInstances, heaps.heaps, rootSite);
+ }
+
+ private static boolean isEndOfHeapDumpSegment(int subtag) {
+ return subtag == 0x1C || subtag == 0x2C;
+ }
+
+ private static class RootData {
+ public long id;
+ public RootType type;
+
+ public RootData(long id, RootType type) {
+ this.id = id;
+ this.type = type;
+ }
+ }
+
+ private static class ClassInstData {
+ // The byte position in the hprof file where instance field data starts.
+ public int position;
+
+ public ClassInstData(int position) {
+ this.position = position;
+ }
+ }
+
+ private static class ObjArrayData {
+ public int length; // Number of array elements.
+ public int position; // Position in hprof file containing element data.
+
+ public ObjArrayData(int length, int position) {
+ this.length = length;
+ this.position = position;
+ }
+ }
+
+ private static class ClassObjData {
+ public long classLoaderId;
+ public FieldValue[] staticFields; // Contains DeferredInstanceValues.
+ }
+
+ /**
+ * Dummy value representing a reference to an instance that has not yet been
+ * resolved.
+ * When first initializing class static fields, we don't yet know what kinds
+ * of objects Object references refer to. We use DeferredInstanceValue as
+ * a dummy kind of value to store the id of an object. In the fixup pass we
+ * resolve all the DeferredInstanceValues into their proper InstanceValues.
+ */
+ private static class DeferredInstanceValue extends Value {
+ private long mId;
+
+ public DeferredInstanceValue(long id) {
+ mId = id;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ @Override
+ protected Type getType() {
+ return Type.OBJECT;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("0x%08x", mId);
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof DeferredInstanceValue) {
+ DeferredInstanceValue value = (DeferredInstanceValue)other;
+ return mId == value.mId;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * A convenient abstraction for lazily building up the list of heaps seen in
+ * the heap dump.
+ */
+ private static class HeapList {
+ public List<AhatHeap> heaps = new ArrayList<AhatHeap>();
+ private AhatHeap current;
+
+ public AhatHeap getCurrentHeap() {
+ if (current == null) {
+ setCurrentHeap("default");
+ }
+ return current;
+ }
+
+ public void setCurrentHeap(String name) {
+ for (AhatHeap heap : heaps) {
+ if (name.equals(heap.getName())) {
+ current = heap;
+ return;
+ }
+ }
+
+ current = new AhatHeap(name, heaps.size());
+ heaps.add(current);
+ }
+ }
+
+ /**
+ * A mapping from id to elements, where certain conditions are
+ * satisfied. The conditions are:
+ * - all elements are defined before they are referenced.
+ * - ids are densely packed in some range [a, b] where a is not
+ * necessarily 0.
+ * - there are not more than 2^31 elements defined.
+ */
+ private static class DenseMap<T> {
+ private String mElementType;
+
+ // mValues behaves like a circular buffer.
+ // mKeyAt0 is the key corresponding to index 0 of mValues. Values with
+ // smaller keys will wrap around to the end of the mValues buffer. The
+ // buffer is expanded when it is no longer big enough to hold all the keys
+ // from mMinKey to mMaxKey.
+ private Object[] mValues;
+ private long mKeyAt0;
+ private long mMaxKey;
+ private long mMinKey;
+
+ /**
+ * Constructs a DenseMap.
+ * @param elementType Human readable name describing the type of
+ * elements for error message if the required
+ * conditions are found not to hold.
+ */
+ public DenseMap(String elementType) {
+ mElementType = elementType;
+ }
+
+ public void put(long key, T value) {
+ if (mValues == null) {
+ mValues = new Object[8];
+ mValues[0] = value;
+ mKeyAt0 = key;
+ mMaxKey = key;
+ mMinKey = key;
+ return;
+ }
+
+ long max = Math.max(mMaxKey, key);
+ long min = Math.min(mMinKey, key);
+ int count = (int)(max + 1 - min);
+ if (count > mValues.length) {
+ Object[] values = new Object[2 * count];
+
+ // Copy over the values into the newly allocated larger buffer. It is
+ // convenient to move the value with mMinKey to index 0 when we make
+ // the copy.
+ for (int i = 0; i < mValues.length; ++i) {
+ values[i] = mValues[indexOf(i + mMinKey)];
+ }
+ mValues = values;
+ mKeyAt0 = mMinKey;
+ }
+ mMinKey = min;
+ mMaxKey = max;
+ mValues[indexOf(key)] = value;
+ }
+
+ /**
+ * Returns the value for the given key.
+ * @throws HprofFormatException if there is no value with the key in the
+ * given map.
+ */
+ public T get(long key) throws HprofFormatException {
+ T value = null;
+ if (mValues != null && key >= mMinKey && key <= mMaxKey) {
+ value = (T)mValues[indexOf(key)];
+ }
+
+ if (value == null) {
+ throw new HprofFormatException(String.format(
+ "%s with id 0x%x referenced before definition", mElementType, key));
+ }
+ return value;
+ }
+
+ private int indexOf(long key) {
+ return ((int)(key - mKeyAt0) + mValues.length) % mValues.length;
+ }
+ }
+
+ /**
+ * A mapping from id to elements, where we don't have nice conditions to
+ * work with.
+ */
+ private static class UnDenseMap<T> {
+ private String mElementType;
+ private Map<Long, T> mValues = new HashMap<Long, T>();
+
+ /**
+ * Constructs an UnDenseMap.
+ * @param elementType Human readable name describing the type of
+ * elements for error message if the required
+ * conditions are found not to hold.
+ */
+ public UnDenseMap(String elementType) {
+ mElementType = elementType;
+ }
+
+ public void put(long key, T value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Returns the value for the given key.
+ * @throws HprofFormatException if there is no value with the key in the
+ * given map.
+ */
+ public T get(long key) throws HprofFormatException {
+ T value = mValues.get(key);
+ if (value == null) {
+ throw new HprofFormatException(String.format(
+ "%s with id 0x%x referenced before definition", mElementType, key));
+ }
+ return value;
+ }
+ }
+
+ /**
+ * Wrapper around a ByteBuffer that presents a uniform interface for
+ * accessing data from an hprof file.
+ */
+ private static class HprofBuffer {
+ private ByteBuffer mBuffer;
+
+ public HprofBuffer(File path) throws IOException {
+ FileChannel channel = FileChannel.open(path.toPath(), StandardOpenOption.READ);
+ mBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
+ channel.close();
+ }
+
+ public HprofBuffer(ByteBuffer buffer) {
+ mBuffer = buffer;
+ }
+
+ public boolean hasRemaining() {
+ return mBuffer.hasRemaining();
+ }
+
+ /**
+ * Return the current absolution position in the file.
+ */
+ public int tell() {
+ return mBuffer.position();
+ }
+
+ /**
+ * Seek to the given absolution position in the file.
+ */
+ public void seek(int position) {
+ mBuffer.position(position);
+ }
+
+ /**
+ * Skip ahead in the file by the given delta bytes. Delta may be negative
+ * to skip backwards in the file.
+ */
+ public void skip(int delta) {
+ seek(tell() + delta);
+ }
+
+ public int getU1() {
+ return mBuffer.get() & 0xFF;
+ }
+
+ public int getU2() {
+ return mBuffer.getShort() & 0xFFFF;
+ }
+
+ public int getU4() {
+ return mBuffer.getInt();
+ }
+
+ public long getId() {
+ return mBuffer.getInt();
+ }
+
+ public boolean getBool() {
+ return mBuffer.get() != 0;
+ }
+
+ public char getChar() {
+ return mBuffer.getChar();
+ }
+
+ public float getFloat() {
+ return mBuffer.getFloat();
+ }
+
+ public double getDouble() {
+ return mBuffer.getDouble();
+ }
+
+ public byte getByte() {
+ return mBuffer.get();
+ }
+
+ public void getBytes(byte[] bytes) {
+ mBuffer.get(bytes);
+ }
+
+ public short getShort() {
+ return mBuffer.getShort();
+ }
+
+ public int getInt() {
+ return mBuffer.getInt();
+ }
+
+ public long getLong() {
+ return mBuffer.getLong();
+ }
+
+ private static Type[] TYPES = new Type[] {
+ null, null, Type.OBJECT, null,
+ Type.BOOLEAN, Type.CHAR, Type.FLOAT, Type.DOUBLE,
+ Type.BYTE, Type.SHORT, Type.INT, Type.LONG
+ };
+
+ public Type getType() throws HprofFormatException {
+ int id = getU1();
+ Type type = id < TYPES.length ? TYPES[id] : null;
+ if (type == null) {
+ throw new HprofFormatException("Invalid basic type id: " + id);
+ }
+ return type;
+ }
+
+ public Type getPrimitiveType() throws HprofFormatException {
+ Type type = getType();
+ if (type == Type.OBJECT) {
+ throw new HprofFormatException("Expected primitive type, but found type 'Object'");
+ }
+ return type;
+ }
+
+ /**
+ * Get a value from the hprof file, using the given instances map to
+ * convert instance ids to their corresponding AhatInstance objects.
+ */
+ public Value getValue(Type type, Instances instances) {
+ switch (type) {
+ case OBJECT: return Value.pack(instances.get(getId()));
+ case BOOLEAN: return Value.pack(getBool());
+ case CHAR: return Value.pack(getChar());
+ case FLOAT: return Value.pack(getFloat());
+ case DOUBLE: return Value.pack(getDouble());
+ case BYTE: return Value.pack(getByte());
+ case SHORT: return Value.pack(getShort());
+ case INT: return Value.pack(getInt());
+ case LONG: return Value.pack(getLong());
+ default: throw new AssertionError("unsupported enum member");
+ }
+ }
+
+ /**
+ * Get a value from the hprof file. AhatInstance values are returned as
+ * DefferredInstanceValues rather than their corresponding AhatInstance
+ * objects.
+ */
+ public Value getDeferredValue(Type type) {
+ switch (type) {
+ case OBJECT: return new DeferredInstanceValue(getId());
+ case BOOLEAN: return Value.pack(getBool());
+ case CHAR: return Value.pack(getChar());
+ case FLOAT: return Value.pack(getFloat());
+ case DOUBLE: return Value.pack(getDouble());
+ case BYTE: return Value.pack(getByte());
+ case SHORT: return Value.pack(getShort());
+ case INT: return Value.pack(getInt());
+ case LONG: return Value.pack(getLong());
+ default: throw new AssertionError("unsupported enum member");
+ }
+ }
+ }
+}
diff --git a/tools/ahat/src/heapdump/RootType.java b/tools/ahat/src/heapdump/RootType.java
new file mode 100644
index 0000000..7165b83
--- /dev/null
+++ b/tools/ahat/src/heapdump/RootType.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+public enum RootType {
+ JNI_GLOBAL (1 << 0),
+ JNI_LOCAL (1 << 1),
+ JAVA_FRAME (1 << 2),
+ NATIVE_STACK (1 << 3),
+ STICKY_CLASS (1 << 4),
+ THREAD_BLOCK (1 << 5),
+ MONITOR (1 << 6),
+ THREAD (1 << 7),
+ INTERNED_STRING (1 << 8),
+ DEBUGGER (1 << 9),
+ VM_INTERNAL (1 << 10),
+ UNKNOWN (1 << 11),
+ JNI_MONITOR (1 << 12);
+
+ public final int mask;
+
+ RootType(int mask) {
+ this.mask = mask;
+ }
+}
diff --git a/tools/ahat/src/heapdump/Site.java b/tools/ahat/src/heapdump/Site.java
index 82931f0..821493f 100644
--- a/tools/ahat/src/heapdump/Site.java
+++ b/tools/ahat/src/heapdump/Site.java
@@ -16,7 +16,7 @@
package com.android.ahat.heapdump;
-import com.android.tools.perflib.heap.StackFrame;
+import com.android.ahat.proguard.ProguardMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -127,27 +127,27 @@
* inner-most frame. May be null, in which case this site is
* returned.
*/
- Site getSite(StackFrame frames[]) {
+ Site getSite(ProguardMap.Frame[] frames) {
return frames == null ? this : getSite(this, frames);
}
- private static Site getSite(Site site, StackFrame frames[]) {
+ private static Site getSite(Site site, ProguardMap.Frame[] frames) {
for (int s = frames.length - 1; s >= 0; --s) {
- StackFrame frame = frames[s];
+ ProguardMap.Frame frame = frames[s];
Site child = null;
for (int i = 0; i < site.mChildren.size(); i++) {
Site curr = site.mChildren.get(i);
- if (curr.mLineNumber == frame.getLineNumber()
- && curr.mMethodName.equals(frame.getMethodName())
- && curr.mSignature.equals(frame.getSignature())
- && curr.mFilename.equals(frame.getFilename())) {
+ if (curr.mLineNumber == frame.line
+ && curr.mMethodName.equals(frame.method)
+ && curr.mSignature.equals(frame.signature)
+ && curr.mFilename.equals(frame.filename)) {
child = curr;
break;
}
}
if (child == null) {
- child = new Site(site, frame.getMethodName(), frame.getSignature(),
- frame.getFilename(), frame.getLineNumber());
+ child = new Site(site, frame.method, frame.signature,
+ frame.filename, frame.line);
site.mChildren.add(child);
}
site = child;
diff --git a/tools/ahat/src/heapdump/SuperRoot.java b/tools/ahat/src/heapdump/SuperRoot.java
index d377113..a2adbd2 100644
--- a/tools/ahat/src/heapdump/SuperRoot.java
+++ b/tools/ahat/src/heapdump/SuperRoot.java
@@ -34,6 +34,11 @@
}
@Override
+ protected long getExtraJavaSize() {
+ return 0;
+ }
+
+ @Override
public String toString() {
return "SUPER_ROOT";
}
diff --git a/tools/ahat/src/heapdump/Type.java b/tools/ahat/src/heapdump/Type.java
new file mode 100644
index 0000000..726bc47
--- /dev/null
+++ b/tools/ahat/src/heapdump/Type.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat.heapdump;
+
+public enum Type {
+ OBJECT("Object", 4),
+ BOOLEAN("boolean", 1),
+ CHAR("char", 2),
+ FLOAT("float", 4),
+ DOUBLE("double", 8),
+ BYTE("byte", 1),
+ SHORT("short", 2),
+ INT("int", 4),
+ LONG("long", 8);
+
+ public final String name;
+ public final int size;
+
+ Type(String name, int size) {
+ this.name = name;
+ this.size = size;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/heapdump/Value.java
index 7f86c01..01fd250 100644
--- a/tools/ahat/src/heapdump/Value.java
+++ b/tools/ahat/src/heapdump/Value.java
@@ -25,37 +25,6 @@
return value == null ? null : new InstanceValue(value);
}
- /**
- * Constructs a value from a generic Java Object.
- * The Object must either be a boxed Java primitive type or a subclass of
- * AhatInstance. The object must not be null.
- */
- public static Value pack(Object object) {
- if (object == null) {
- return null;
- } else if (object instanceof AhatInstance) {
- return Value.pack((AhatInstance)object);
- } else if (object instanceof Boolean) {
- return Value.pack(((Boolean)object).booleanValue());
- } else if (object instanceof Character) {
- return Value.pack(((Character)object).charValue());
- } else if (object instanceof Float) {
- return Value.pack(((Float)object).floatValue());
- } else if (object instanceof Double) {
- return Value.pack(((Double)object).doubleValue());
- } else if (object instanceof Byte) {
- return Value.pack(((Byte)object).byteValue());
- } else if (object instanceof Short) {
- return Value.pack(((Short)object).shortValue());
- } else if (object instanceof Integer) {
- return Value.pack(((Integer)object).intValue());
- } else if (object instanceof Long) {
- return Value.pack(((Long)object).longValue());
- }
- throw new IllegalArgumentException(
- "AhatInstance or primitive type required, but got: " + object.toString());
- }
-
public static Value pack(boolean value) {
return new BooleanValue(value);
}
@@ -89,6 +58,18 @@
}
/**
+ * Return the type of the given value.
+ */
+ public static Type getType(Value value) {
+ return value == null ? Type.OBJECT : value.getType();
+ }
+
+ /**
+ * Return the type of the given value.
+ */
+ protected abstract Type getType();
+
+ /**
* Returns true if the Value is an AhatInstance, as opposed to a Java
* primitive value.
*/
@@ -172,6 +153,11 @@
}
@Override
+ protected Type getType() {
+ return Type.BOOLEAN;
+ }
+
+ @Override
public String toString() {
return Boolean.toString(mBool);
}
@@ -198,6 +184,11 @@
}
@Override
+ protected Type getType() {
+ return Type.BYTE;
+ }
+
+ @Override
public String toString() {
return Byte.toString(mByte);
}
@@ -224,6 +215,11 @@
}
@Override
+ protected Type getType() {
+ return Type.CHAR;
+ }
+
+ @Override
public String toString() {
return Character.toString(mChar);
}
@@ -245,6 +241,11 @@
}
@Override
+ protected Type getType() {
+ return Type.DOUBLE;
+ }
+
+ @Override
public String toString() {
return Double.toString(mDouble);
}
@@ -266,6 +267,11 @@
}
@Override
+ protected Type getType() {
+ return Type.FLOAT;
+ }
+
+ @Override
public String toString() {
return Float.toString(mFloat);
}
@@ -298,6 +304,11 @@
}
@Override
+ protected Type getType() {
+ return Type.OBJECT;
+ }
+
+ @Override
public String toString() {
return mInstance.toString();
}
@@ -334,6 +345,11 @@
}
@Override
+ protected Type getType() {
+ return Type.INT;
+ }
+
+ @Override
public String toString() {
return Integer.toString(mInt);
}
@@ -365,6 +381,11 @@
}
@Override
+ protected Type getType() {
+ return Type.LONG;
+ }
+
+ @Override
public String toString() {
return Long.toString(mLong);
}
@@ -386,6 +407,11 @@
}
@Override
+ protected Type getType() {
+ return Type.SHORT;
+ }
+
+ @Override
public String toString() {
return Short.toString(mShort);
}
diff --git a/tools/ahat/src/proguard/ProguardMap.java b/tools/ahat/src/proguard/ProguardMap.java
new file mode 100644
index 0000000..50c110a
--- /dev/null
+++ b/tools/ahat/src/proguard/ProguardMap.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2016 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.ahat.proguard;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Map;
+
+// Class used to deobfuscate classes, fields, and stack frames.
+public class ProguardMap {
+
+ private static final String ARRAY_SYMBOL = "[]";
+
+ private static class FrameData {
+ public FrameData(String clearMethodName, int lineDelta) {
+ this.clearMethodName = clearMethodName;
+ this.lineDelta = lineDelta;
+ }
+
+ public final String clearMethodName;
+ public final int lineDelta; // lineDelta = obfuscatedLine - clearLine
+ }
+
+ private static class ClassData {
+ private final String mClearName;
+
+ // Mapping from obfuscated field name to clear field name.
+ private final Map<String, String> mFields = new HashMap<String, String>();
+
+ // obfuscatedMethodName + clearSignature -> FrameData
+ private final Map<String, FrameData> mFrames = new HashMap<String, FrameData>();
+
+ // Constructs a ClassData object for a class with the given clear name.
+ public ClassData(String clearName) {
+ mClearName = clearName;
+ }
+
+ // Returns the clear name of the class.
+ public String getClearName() {
+ return mClearName;
+ }
+
+ public void addField(String obfuscatedName, String clearName) {
+ mFields.put(obfuscatedName, clearName);
+ }
+
+ // Get the clear name for the field in this class with the given
+ // obfuscated name. Returns the original obfuscated name if a clear
+ // name for the field could not be determined.
+ // TODO: Do we need to take into account the type of the field to
+ // propery determine the clear name?
+ public String getField(String obfuscatedName) {
+ String clearField = mFields.get(obfuscatedName);
+ return clearField == null ? obfuscatedName : clearField;
+ }
+
+ // TODO: Does this properly interpret the meaning of line numbers? Is
+ // it possible to have multiple frame entries for the same method
+ // name and signature that differ only by line ranges?
+ public void addFrame(String obfuscatedMethodName, String clearMethodName,
+ String clearSignature, int obfuscatedLine, int clearLine) {
+ String key = obfuscatedMethodName + clearSignature;
+ mFrames.put(key, new FrameData(clearMethodName, obfuscatedLine - clearLine));
+ }
+
+ public Frame getFrame(String clearClassName, String obfuscatedMethodName,
+ String clearSignature, String obfuscatedFilename, int obfuscatedLine) {
+ String key = obfuscatedMethodName + clearSignature;
+ FrameData frame = mFrames.get(key);
+ if (frame == null) {
+ return new Frame(obfuscatedMethodName, clearSignature,
+ obfuscatedFilename, obfuscatedLine);
+ }
+ return new Frame(frame.clearMethodName, clearSignature,
+ getFileName(clearClassName, frame.clearMethodName),
+ obfuscatedLine - frame.lineDelta);
+ }
+ }
+
+ private Map<String, ClassData> mClassesFromClearName = new HashMap<String, ClassData>();
+ private Map<String, ClassData> mClassesFromObfuscatedName = new HashMap<String, ClassData>();
+
+ public static class Frame {
+ public Frame(String method, String signature, String filename, int line) {
+ this.method = method;
+ this.signature = signature;
+ this.filename = filename;
+ this.line = line;
+ }
+
+ public final String method;
+ public final String signature;
+ public final String filename;
+ public final int line;
+ }
+
+ private static void parseException(String msg) throws ParseException {
+ throw new ParseException(msg, 0);
+ }
+
+ // Read in proguard mapping information from the given file.
+ public void readFromFile(File mapFile)
+ throws FileNotFoundException, IOException, ParseException {
+ readFromReader(new FileReader(mapFile));
+ }
+
+ // Read in proguard mapping information from the given Reader.
+ public void readFromReader(Reader mapReader) throws IOException, ParseException {
+ BufferedReader reader = new BufferedReader(mapReader);
+ String line = reader.readLine();
+ while (line != null) {
+ // Class lines are of the form:
+ // 'clear.class.name -> obfuscated_class_name:'
+ int sep = line.indexOf(" -> ");
+ if (sep == -1 || sep + 5 >= line.length()) {
+ parseException("Error parsing class line: '" + line + "'");
+ }
+ String clearClassName = line.substring(0, sep);
+ String obfuscatedClassName = line.substring(sep + 4, line.length() - 1);
+
+ ClassData classData = new ClassData(clearClassName);
+ mClassesFromClearName.put(clearClassName, classData);
+ mClassesFromObfuscatedName.put(obfuscatedClassName, classData);
+
+ // After the class line comes zero or more field/method lines of the form:
+ // ' type clearName -> obfuscatedName'
+ line = reader.readLine();
+ while (line != null && line.startsWith(" ")) {
+ String trimmed = line.trim();
+ int ws = trimmed.indexOf(' ');
+ sep = trimmed.indexOf(" -> ");
+ if (ws == -1 || sep == -1) {
+ parseException("Error parse field/method line: '" + line + "'");
+ }
+
+ String type = trimmed.substring(0, ws);
+ String clearName = trimmed.substring(ws + 1, sep);
+ String obfuscatedName = trimmed.substring(sep + 4, trimmed.length());
+
+ // If the clearName contains '(', then this is for a method instead of a
+ // field.
+ if (clearName.indexOf('(') == -1) {
+ classData.addField(obfuscatedName, clearName);
+ } else {
+ // For methods, the type is of the form: [#:[#:]]<returnType>
+ int obfuscatedLine = 0;
+ int colon = type.indexOf(':');
+ if (colon != -1) {
+ obfuscatedLine = Integer.parseInt(type.substring(0, colon));
+ type = type.substring(colon + 1);
+ }
+ colon = type.indexOf(':');
+ if (colon != -1) {
+ type = type.substring(colon + 1);
+ }
+
+ // For methods, the clearName is of the form: <clearName><sig>[:#[:#]]
+ int op = clearName.indexOf('(');
+ int cp = clearName.indexOf(')');
+ if (op == -1 || cp == -1) {
+ parseException("Error parse method line: '" + line + "'");
+ }
+
+ String sig = clearName.substring(op, cp + 1);
+
+ int clearLine = obfuscatedLine;
+ colon = clearName.lastIndexOf(':');
+ if (colon != -1) {
+ clearLine = Integer.parseInt(clearName.substring(colon + 1));
+ clearName = clearName.substring(0, colon);
+ }
+
+ colon = clearName.lastIndexOf(':');
+ if (colon != -1) {
+ clearLine = Integer.parseInt(clearName.substring(colon + 1));
+ clearName = clearName.substring(0, colon);
+ }
+
+ clearName = clearName.substring(0, op);
+
+ String clearSig = fromProguardSignature(sig + type);
+ classData.addFrame(obfuscatedName, clearName, clearSig,
+ obfuscatedLine, clearLine);
+ }
+
+ line = reader.readLine();
+ }
+ }
+ reader.close();
+ }
+
+ // Returns the deobfuscated version of the given class name. If no
+ // deobfuscated version is known, the original string is returned.
+ public String getClassName(String obfuscatedClassName) {
+ // Class names for arrays may have trailing [] that need to be
+ // stripped before doing the lookup.
+ String baseName = obfuscatedClassName;
+ String arraySuffix = "";
+ while (baseName.endsWith(ARRAY_SYMBOL)) {
+ arraySuffix += ARRAY_SYMBOL;
+ baseName = baseName.substring(0, baseName.length() - ARRAY_SYMBOL.length());
+ }
+
+ ClassData classData = mClassesFromObfuscatedName.get(baseName);
+ String clearBaseName = classData == null ? baseName : classData.getClearName();
+ return clearBaseName + arraySuffix;
+ }
+
+ // Returns the deobfuscated version of the given field name for the given
+ // (clear) class name. If no deobfuscated version is known, the original
+ // string is returned.
+ public String getFieldName(String clearClass, String obfuscatedField) {
+ ClassData classData = mClassesFromClearName.get(clearClass);
+ if (classData == null) {
+ return obfuscatedField;
+ }
+ return classData.getField(obfuscatedField);
+ }
+
+ // Returns the deobfuscated frame for the given obfuscated frame and (clear)
+ // class name. As much of the frame is deobfuscated as can be.
+ public Frame getFrame(String clearClassName, String obfuscatedMethodName,
+ String obfuscatedSignature, String obfuscatedFilename, int obfuscatedLine) {
+ String clearSignature = getSignature(obfuscatedSignature);
+ ClassData classData = mClassesFromClearName.get(clearClassName);
+ if (classData == null) {
+ return new Frame(obfuscatedMethodName, clearSignature,
+ obfuscatedFilename, obfuscatedLine);
+ }
+ return classData.getFrame(clearClassName, obfuscatedMethodName, clearSignature,
+ obfuscatedFilename, obfuscatedLine);
+ }
+
+ // Converts a proguard-formatted method signature into a Java formatted
+ // method signature.
+ private static String fromProguardSignature(String sig) throws ParseException {
+ if (sig.startsWith("(")) {
+ int end = sig.indexOf(')');
+ if (end == -1) {
+ parseException("Error parsing signature: " + sig);
+ }
+
+ StringBuilder converted = new StringBuilder();
+ converted.append('(');
+ if (end > 1) {
+ for (String arg : sig.substring(1, end).split(",")) {
+ converted.append(fromProguardSignature(arg));
+ }
+ }
+ converted.append(')');
+ converted.append(fromProguardSignature(sig.substring(end + 1)));
+ return converted.toString();
+ } else if (sig.endsWith(ARRAY_SYMBOL)) {
+ return "[" + fromProguardSignature(sig.substring(0, sig.length() - 2));
+ } else if (sig.equals("boolean")) {
+ return "Z";
+ } else if (sig.equals("byte")) {
+ return "B";
+ } else if (sig.equals("char")) {
+ return "C";
+ } else if (sig.equals("short")) {
+ return "S";
+ } else if (sig.equals("int")) {
+ return "I";
+ } else if (sig.equals("long")) {
+ return "J";
+ } else if (sig.equals("float")) {
+ return "F";
+ } else if (sig.equals("double")) {
+ return "D";
+ } else if (sig.equals("void")) {
+ return "V";
+ } else {
+ return "L" + sig.replace('.', '/') + ";";
+ }
+ }
+
+ // Return a clear signature for the given obfuscated signature.
+ private String getSignature(String obfuscatedSig) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < obfuscatedSig.length(); i++) {
+ if (obfuscatedSig.charAt(i) == 'L') {
+ int e = obfuscatedSig.indexOf(';', i);
+ builder.append('L');
+ String cls = obfuscatedSig.substring(i + 1, e).replace('/', '.');
+ builder.append(getClassName(cls).replace('.', '/'));
+ builder.append(';');
+ i = e;
+ } else {
+ builder.append(obfuscatedSig.charAt(i));
+ }
+ }
+ return builder.toString();
+ }
+
+ // Return a file name for the given clear class name and method.
+ private static String getFileName(String clearClass, String method) {
+ int dot = method.lastIndexOf('.');
+ if (dot != -1) {
+ clearClass = method.substring(0, dot);
+ }
+
+ String filename = clearClass;
+ dot = filename.lastIndexOf('.');
+ if (dot != -1) {
+ filename = filename.substring(dot + 1);
+ }
+
+ int dollar = filename.indexOf('$');
+ if (dollar != -1) {
+ filename = filename.substring(0, dollar);
+ }
+ return filename + ".java";
+ }
+}
diff --git a/tools/ahat/test/DiffFieldsTest.java b/tools/ahat/test/DiffFieldsTest.java
index 7dc519d..1939975 100644
--- a/tools/ahat/test/DiffFieldsTest.java
+++ b/tools/ahat/test/DiffFieldsTest.java
@@ -19,6 +19,7 @@
import com.android.ahat.heapdump.DiffFields;
import com.android.ahat.heapdump.DiffedFieldValue;
import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.Type;
import com.android.ahat.heapdump.Value;
import java.util.ArrayList;
import java.util.List;
@@ -28,14 +29,25 @@
import static org.junit.Assert.assertNull;
public class DiffFieldsTest {
+ // Give more convenient abstract names for different types.
+ private static final Type t0 = Type.OBJECT;
+ private static final Type t1 = Type.BOOLEAN;
+ private static final Type t2 = Type.CHAR;
+ private static final Type t3 = Type.FLOAT;
+ private static final Type t4 = Type.DOUBLE;
+ private static final Type t5 = Type.BYTE;
+ private static final Type t6 = Type.SHORT;
+ private static final Type t7 = Type.INT;
+ private static final Type t8 = Type.LONG;
+
@Test
public void normalMatchedDiffedFieldValues() {
- FieldValue normal1 = new FieldValue("name", "type", Value.pack(1));
- FieldValue normal2 = new FieldValue("name", "type", Value.pack(2));
+ FieldValue normal1 = new FieldValue("name", t0, Value.pack(1));
+ FieldValue normal2 = new FieldValue("name", t0, Value.pack(2));
DiffedFieldValue x = DiffedFieldValue.matched(normal1, normal2);
assertEquals("name", x.name);
- assertEquals("type", x.type);
+ assertEquals(t0, x.type);
assertEquals(Value.pack(1), x.current);
assertEquals(Value.pack(2), x.baseline);
assertEquals(DiffedFieldValue.Status.MATCHED, x.status);
@@ -43,19 +55,19 @@
@Test
public void nulledMatchedDiffedFieldValues() {
- FieldValue normal = new FieldValue("name", "type", Value.pack(1));
- FieldValue nulled = new FieldValue("name", "type", null);
+ FieldValue normal = new FieldValue("name", t0, Value.pack(1));
+ FieldValue nulled = new FieldValue("name", t0, null);
DiffedFieldValue x = DiffedFieldValue.matched(normal, nulled);
assertEquals("name", x.name);
- assertEquals("type", x.type);
+ assertEquals(t0, x.type);
assertEquals(Value.pack(1), x.current);
assertNull(x.baseline);
assertEquals(DiffedFieldValue.Status.MATCHED, x.status);
DiffedFieldValue y = DiffedFieldValue.matched(nulled, normal);
assertEquals("name", y.name);
- assertEquals("type", y.type);
+ assertEquals(t0, y.type);
assertNull(y.current);
assertEquals(Value.pack(1), y.baseline);
assertEquals(DiffedFieldValue.Status.MATCHED, y.status);
@@ -63,44 +75,44 @@
@Test
public void normalAddedDiffedFieldValues() {
- FieldValue normal = new FieldValue("name", "type", Value.pack(1));
+ FieldValue normal = new FieldValue("name", t0, Value.pack(1));
DiffedFieldValue x = DiffedFieldValue.added(normal);
assertEquals("name", x.name);
- assertEquals("type", x.type);
+ assertEquals(t0, x.type);
assertEquals(Value.pack(1), x.current);
assertEquals(DiffedFieldValue.Status.ADDED, x.status);
}
@Test
public void nulledAddedDiffedFieldValues() {
- FieldValue nulled = new FieldValue("name", "type", null);
+ FieldValue nulled = new FieldValue("name", t0, null);
DiffedFieldValue x = DiffedFieldValue.added(nulled);
assertEquals("name", x.name);
- assertEquals("type", x.type);
+ assertEquals(t0, x.type);
assertNull(x.current);
assertEquals(DiffedFieldValue.Status.ADDED, x.status);
}
@Test
public void normalDeletedDiffedFieldValues() {
- FieldValue normal = new FieldValue("name", "type", Value.pack(1));
+ FieldValue normal = new FieldValue("name", t0, Value.pack(1));
DiffedFieldValue x = DiffedFieldValue.deleted(normal);
assertEquals("name", x.name);
- assertEquals("type", x.type);
+ assertEquals(t0, x.type);
assertEquals(Value.pack(1), x.baseline);
assertEquals(DiffedFieldValue.Status.DELETED, x.status);
}
@Test
public void nulledDeletedDiffedFieldValues() {
- FieldValue nulled = new FieldValue("name", "type", null);
+ FieldValue nulled = new FieldValue("name", t0, null);
DiffedFieldValue x = DiffedFieldValue.deleted(nulled);
assertEquals("name", x.name);
- assertEquals("type", x.type);
+ assertEquals(t0, x.type);
assertNull(x.baseline);
assertEquals(DiffedFieldValue.Status.DELETED, x.status);
}
@@ -108,21 +120,21 @@
@Test
public void basicDiff() {
List<FieldValue> a = new ArrayList<FieldValue>();
- a.add(new FieldValue("n0", "t0", null));
- a.add(new FieldValue("n2", "t2", null));
- a.add(new FieldValue("n3", "t3", null));
- a.add(new FieldValue("n4", "t4", null));
- a.add(new FieldValue("n5", "t5", null));
- a.add(new FieldValue("n6", "t6", null));
+ a.add(new FieldValue("n0", t0, null));
+ a.add(new FieldValue("n2", t2, null));
+ a.add(new FieldValue("n3", t3, null));
+ a.add(new FieldValue("n4", t4, null));
+ a.add(new FieldValue("n5", t5, null));
+ a.add(new FieldValue("n6", t6, null));
List<FieldValue> b = new ArrayList<FieldValue>();
- b.add(new FieldValue("n0", "t0", null));
- b.add(new FieldValue("n1", "t1", null));
- b.add(new FieldValue("n2", "t2", null));
- b.add(new FieldValue("n3", "t3", null));
- b.add(new FieldValue("n5", "t5", null));
- b.add(new FieldValue("n6", "t6", null));
- b.add(new FieldValue("n7", "t7", null));
+ b.add(new FieldValue("n0", t0, null));
+ b.add(new FieldValue("n1", t1, null));
+ b.add(new FieldValue("n2", t2, null));
+ b.add(new FieldValue("n3", t3, null));
+ b.add(new FieldValue("n5", t5, null));
+ b.add(new FieldValue("n6", t6, null));
+ b.add(new FieldValue("n7", t7, null));
// Note: The expected result makes assumptions about the implementation of
// field diff to match the order of the returned fields. If the
@@ -145,22 +157,22 @@
@Test
public void reorderedDiff() {
List<FieldValue> a = new ArrayList<FieldValue>();
- a.add(new FieldValue("n0", "t0", null));
- a.add(new FieldValue("n1", "t1", null));
- a.add(new FieldValue("n2", "t2", null));
- a.add(new FieldValue("n3", "t3", null));
- a.add(new FieldValue("n4", "t4", null));
- a.add(new FieldValue("n5", "t5", null));
- a.add(new FieldValue("n6", "t6", null));
+ a.add(new FieldValue("n0", t0, null));
+ a.add(new FieldValue("n1", t1, null));
+ a.add(new FieldValue("n2", t2, null));
+ a.add(new FieldValue("n3", t3, null));
+ a.add(new FieldValue("n4", t4, null));
+ a.add(new FieldValue("n5", t5, null));
+ a.add(new FieldValue("n6", t6, null));
List<FieldValue> b = new ArrayList<FieldValue>();
- b.add(new FieldValue("n4", "t4", null));
- b.add(new FieldValue("n1", "t1", null));
- b.add(new FieldValue("n3", "t3", null));
- b.add(new FieldValue("n0", "t0", null));
- b.add(new FieldValue("n5", "t5", null));
- b.add(new FieldValue("n2", "t2", null));
- b.add(new FieldValue("n6", "t6", null));
+ b.add(new FieldValue("n4", t4, null));
+ b.add(new FieldValue("n1", t1, null));
+ b.add(new FieldValue("n3", t3, null));
+ b.add(new FieldValue("n0", t0, null));
+ b.add(new FieldValue("n5", t5, null));
+ b.add(new FieldValue("n2", t2, null));
+ b.add(new FieldValue("n6", t6, null));
// Note: The expected result makes assumptions about the implementation of
// field diff to match the order of the returned fields. If the
diff --git a/tools/ahat/test/DiffTest.java b/tools/ahat/test/DiffTest.java
index cfd0236..585f29a 100644
--- a/tools/ahat/test/DiffTest.java
+++ b/tools/ahat/test/DiffTest.java
@@ -18,26 +18,7 @@
import com.android.ahat.heapdump.AhatHeap;
import com.android.ahat.heapdump.AhatInstance;
-import com.android.ahat.heapdump.AhatSnapshot;
-import com.android.ahat.heapdump.Diff;
-import com.android.tools.perflib.heap.hprof.HprofClassDump;
-import com.android.tools.perflib.heap.hprof.HprofConstant;
-import com.android.tools.perflib.heap.hprof.HprofDumpRecord;
-import com.android.tools.perflib.heap.hprof.HprofHeapDump;
-import com.android.tools.perflib.heap.hprof.HprofInstanceDump;
-import com.android.tools.perflib.heap.hprof.HprofInstanceField;
-import com.android.tools.perflib.heap.hprof.HprofLoadClass;
-import com.android.tools.perflib.heap.hprof.HprofPrimitiveArrayDump;
-import com.android.tools.perflib.heap.hprof.HprofRecord;
-import com.android.tools.perflib.heap.hprof.HprofRootDebugger;
-import com.android.tools.perflib.heap.hprof.HprofStaticField;
-import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
-import com.android.tools.perflib.heap.hprof.HprofType;
-import com.google.common.io.ByteArrayDataOutput;
-import com.google.common.io.ByteStreams;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -98,41 +79,4 @@
AhatHandler handler = new ObjectsHandler(dump.getAhatSnapshot());
TestHandler.testNoCrash(handler, "http://localhost:7100/objects?class=java.lang.Class");
}
-
- @Test
- public void nullClassObj() throws IOException {
- // Set up a heap dump that has a null classObj.
- // The heap dump is derived from the InstanceTest.asStringEmbedded test.
- HprofStringBuilder strings = new HprofStringBuilder(0);
- List<HprofRecord> records = new ArrayList<HprofRecord>();
- List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
-
- final int stringClassObjectId = 1;
- records.add(new HprofLoadClass(0, 0, stringClassObjectId, 0, strings.get("java.lang.String")));
- dump.add(new HprofClassDump(stringClassObjectId, 0, 0, 0, 0, 0, 0, 0, 0,
- new HprofConstant[0], new HprofStaticField[0],
- new HprofInstanceField[]{
- new HprofInstanceField(strings.get("count"), HprofType.TYPE_INT),
- new HprofInstanceField(strings.get("hashCode"), HprofType.TYPE_INT),
- new HprofInstanceField(strings.get("offset"), HprofType.TYPE_INT),
- new HprofInstanceField(strings.get("value"), HprofType.TYPE_OBJECT)}));
-
- dump.add(new HprofPrimitiveArrayDump(0x41, 0, HprofType.TYPE_CHAR,
- new long[]{'n', 'o', 't', ' ', 'h', 'e', 'l', 'l', 'o', 'o', 'p'}));
-
- ByteArrayDataOutput values = ByteStreams.newDataOutput();
- values.writeInt(5); // count
- values.writeInt(0); // hashCode
- values.writeInt(4); // offset
- values.writeInt(0x41); // value
- dump.add(new HprofInstanceDump(0x42, 0, stringClassObjectId, values.toByteArray()));
- dump.add(new HprofRootDebugger(stringClassObjectId));
- dump.add(new HprofRootDebugger(0x42));
-
- records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
- AhatSnapshot snapshot = SnapshotBuilder.makeSnapshot(strings, records);
-
- // Diffing should not crash.
- Diff.snapshots(snapshot, snapshot);
- }
}
diff --git a/tools/ahat/test/ProguardMapTest.java b/tools/ahat/test/ProguardMapTest.java
new file mode 100644
index 0000000..ad40f45
--- /dev/null
+++ b/tools/ahat/test/ProguardMapTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.proguard.ProguardMap;
+import java.io.IOException;
+import java.io.StringReader;
+import java.text.ParseException;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class ProguardMapTest {
+ private static final String TEST_MAP =
+ "class.that.is.Empty -> a:\n"
+ + "class.that.is.Empty$subclass -> b:\n"
+ + "class.with.only.Fields -> c:\n"
+ + " int prim_type_field -> a\n"
+ + " int[] prim_array_type_field -> b\n"
+ + " class.that.is.Empty class_type_field -> c\n"
+ + " class.that.is.Empty[] array_type_field -> d\n"
+ + " int longObfuscatedNameField -> abc\n"
+ + "class.with.Methods -> d:\n"
+ + " int some_field -> a\n"
+ + " 12:23:void <clinit>() -> <clinit>\n"
+ + " 42:43:void boringMethod() -> m\n"
+ + " 45:48:void methodWithPrimArgs(int,float) -> m\n"
+ + " 49:50:void methodWithPrimArrArgs(int[],float) -> m\n"
+ + " 52:55:void methodWithClearObjArg(class.not.in.Map) -> m\n"
+ + " 57:58:void methodWithClearObjArrArg(class.not.in.Map[]) -> m\n"
+ + " 59:61:void methodWithObfObjArg(class.with.only.Fields) -> m\n"
+ + " 64:66:class.with.only.Fields methodWithObfRes() -> n\n"
+ + " 80:80:void lineObfuscatedMethod():8:8 -> o\n"
+ + " 90:90:void lineObfuscatedMethod2():9 -> p\n"
+ + " 120:121:void method.from.a.Superclass.supermethod() -> q\n"
+ ;
+
+ @Test
+ public void proguardMap() throws IOException, ParseException {
+ ProguardMap map = new ProguardMap();
+
+ // An empty proguard map should not deobfuscate anything.
+ assertEquals("foo.bar.Sludge", map.getClassName("foo.bar.Sludge"));
+ assertEquals("fooBarSludge", map.getClassName("fooBarSludge"));
+ assertEquals("myfield", map.getFieldName("foo.bar.Sludge", "myfield"));
+ assertEquals("myfield", map.getFieldName("fooBarSludge", "myfield"));
+ ProguardMap.Frame frame = map.getFrame(
+ "foo.bar.Sludge", "mymethod", "(Lfoo/bar/Sludge;)V", "SourceFile.java", 123);
+ assertEquals("mymethod", frame.method);
+ assertEquals("(Lfoo/bar/Sludge;)V", frame.signature);
+ assertEquals("SourceFile.java", frame.filename);
+ assertEquals(123, frame.line);
+
+ // Read in the proguard map.
+ map.readFromReader(new StringReader(TEST_MAP));
+
+ // It should still not deobfuscate things that aren't in the map
+ assertEquals("foo.bar.Sludge", map.getClassName("foo.bar.Sludge"));
+ assertEquals("fooBarSludge", map.getClassName("fooBarSludge"));
+ assertEquals("myfield", map.getFieldName("foo.bar.Sludge", "myfield"));
+ assertEquals("myfield", map.getFieldName("fooBarSludge", "myfield"));
+ frame = map.getFrame("foo.bar.Sludge", "mymethod", "(Lfoo/bar/Sludge;)V",
+ "SourceFile.java", 123);
+ assertEquals("mymethod", frame.method);
+ assertEquals("(Lfoo/bar/Sludge;)V", frame.signature);
+ assertEquals("SourceFile.java", frame.filename);
+ assertEquals(123, frame.line);
+
+ // Test deobfuscation of class names
+ assertEquals("class.that.is.Empty", map.getClassName("a"));
+ assertEquals("class.that.is.Empty$subclass", map.getClassName("b"));
+ assertEquals("class.with.only.Fields", map.getClassName("c"));
+ assertEquals("class.with.Methods", map.getClassName("d"));
+
+ // Test deobfuscation of array classes.
+ assertEquals("class.with.Methods[]", map.getClassName("d[]"));
+ assertEquals("class.with.Methods[][]", map.getClassName("d[][]"));
+
+ // Test deobfuscation of methods
+ assertEquals("prim_type_field", map.getFieldName("class.with.only.Fields", "a"));
+ assertEquals("prim_array_type_field", map.getFieldName("class.with.only.Fields", "b"));
+ assertEquals("class_type_field", map.getFieldName("class.with.only.Fields", "c"));
+ assertEquals("array_type_field", map.getFieldName("class.with.only.Fields", "d"));
+ assertEquals("longObfuscatedNameField", map.getFieldName("class.with.only.Fields", "abc"));
+ assertEquals("some_field", map.getFieldName("class.with.Methods", "a"));
+
+ // Test deobfuscation of frames
+ frame = map.getFrame("class.with.Methods", "<clinit>", "()V", "SourceFile.java", 13);
+ assertEquals("<clinit>", frame.method);
+ assertEquals("()V", frame.signature);
+ assertEquals("Methods.java", frame.filename);
+ assertEquals(13, frame.line);
+
+ frame = map.getFrame("class.with.Methods", "m", "()V", "SourceFile.java", 42);
+ assertEquals("boringMethod", frame.method);
+ assertEquals("()V", frame.signature);
+ assertEquals("Methods.java", frame.filename);
+ assertEquals(42, frame.line);
+
+ frame = map.getFrame("class.with.Methods", "m", "(IF)V", "SourceFile.java", 45);
+ assertEquals("methodWithPrimArgs", frame.method);
+ assertEquals("(IF)V", frame.signature);
+ assertEquals("Methods.java", frame.filename);
+ assertEquals(45, frame.line);
+
+ frame = map.getFrame("class.with.Methods", "m", "([IF)V", "SourceFile.java", 49);
+ assertEquals("methodWithPrimArrArgs", frame.method);
+ assertEquals("([IF)V", frame.signature);
+ assertEquals("Methods.java", frame.filename);
+ assertEquals(49, frame.line);
+
+ frame = map.getFrame("class.with.Methods", "m", "(Lclass/not/in/Map;)V",
+ "SourceFile.java", 52);
+ assertEquals("methodWithClearObjArg", frame.method);
+ assertEquals("(Lclass/not/in/Map;)V", frame.signature);
+ assertEquals("Methods.java", frame.filename);
+ assertEquals(52, frame.line);
+
+ frame = map.getFrame("class.with.Methods", "m", "([Lclass/not/in/Map;)V",
+ "SourceFile.java", 57);
+ assertEquals("methodWithClearObjArrArg", frame.method);
+ assertEquals("([Lclass/not/in/Map;)V", frame.signature);
+ assertEquals("Methods.java", frame.filename);
+ assertEquals(57, frame.line);
+
+ frame = map.getFrame("class.with.Methods", "m", "(Lc;)V", "SourceFile.java", 59);
+ assertEquals("methodWithObfObjArg", frame.method);
+ assertEquals("(Lclass/with/only/Fields;)V", frame.signature);
+ assertEquals("Methods.java", frame.filename);
+ assertEquals(59, frame.line);
+
+ frame = map.getFrame("class.with.Methods", "n", "()Lc;", "SourceFile.java", 64);
+ assertEquals("methodWithObfRes", frame.method);
+ assertEquals("()Lclass/with/only/Fields;", frame.signature);
+ assertEquals("Methods.java", frame.filename);
+ assertEquals(64, frame.line);
+
+ frame = map.getFrame("class.with.Methods", "o", "()V", "SourceFile.java", 80);
+ assertEquals("lineObfuscatedMethod", frame.method);
+ assertEquals("()V", frame.signature);
+ assertEquals("Methods.java", frame.filename);
+ assertEquals(8, frame.line);
+
+ frame = map.getFrame("class.with.Methods", "p", "()V", "SourceFile.java", 94);
+ assertEquals("lineObfuscatedMethod2", frame.method);
+ assertEquals("()V", frame.signature);
+ assertEquals("Methods.java", frame.filename);
+ assertEquals(13, frame.line);
+
+ frame = map.getFrame("class.with.Methods", "q", "()V", "SourceFile.java", 120);
+ // TODO: Should this be "supermethod", instead of
+ // "method.from.a.Superclass.supermethod"?
+ assertEquals("method.from.a.Superclass.supermethod", frame.method);
+ assertEquals("()V", frame.signature);
+ assertEquals("Superclass.java", frame.filename);
+ assertEquals(120, frame.line);
+ }
+}
diff --git a/tools/ahat/test/SnapshotBuilder.java b/tools/ahat/test/SnapshotBuilder.java
deleted file mode 100644
index 0eea635..0000000
--- a/tools/ahat/test/SnapshotBuilder.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ahat;
-
-import com.android.ahat.heapdump.AhatSnapshot;
-import com.android.tools.perflib.heap.ProguardMap;
-import com.android.tools.perflib.heap.hprof.Hprof;
-import com.android.tools.perflib.heap.hprof.HprofRecord;
-import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
-import com.android.tools.perflib.heap.io.InMemoryBuffer;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Class with utilities to help constructing snapshots for tests.
- */
-public class SnapshotBuilder {
-
- // Helper function to make a snapshot with id size 4 given an
- // HprofStringBuilder and list of HprofRecords
- public static AhatSnapshot makeSnapshot(HprofStringBuilder strings, List<HprofRecord> records)
- throws IOException {
- // TODO: When perflib can handle the case where strings are referred to
- // before they are defined, just add the string records to the records
- // list.
- List<HprofRecord> actualRecords = new ArrayList<HprofRecord>();
- actualRecords.addAll(strings.getStringRecords());
- actualRecords.addAll(records);
-
- Hprof hprof = new Hprof("JAVA PROFILE 1.0.3", 4, new Date(), actualRecords);
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- hprof.write(os);
- InMemoryBuffer buffer = new InMemoryBuffer(os.toByteArray());
- return AhatSnapshot.fromDataBuffer(buffer, new ProguardMap());
- }
-}
diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java
index d1afe43..a0d1021 100644
--- a/tools/ahat/test/TestDump.java
+++ b/tools/ahat/test/TestDump.java
@@ -21,15 +21,16 @@
import com.android.ahat.heapdump.AhatSnapshot;
import com.android.ahat.heapdump.Diff;
import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.HprofFormatException;
+import com.android.ahat.heapdump.Parser;
import com.android.ahat.heapdump.Site;
import com.android.ahat.heapdump.Value;
-import com.android.tools.perflib.captures.DataBuffer;
-import com.android.tools.perflib.heap.ProguardMap;
-import com.android.tools.perflib.heap.io.InMemoryBuffer;
+import com.android.ahat.proguard.ProguardMap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
@@ -70,9 +71,9 @@
private AhatClassObj mBaselineMain;
/**
- * Read the named resource into a DataBuffer.
+ * Read the named resource into a ByteBuffer.
*/
- private static DataBuffer dataBufferFromResource(String name) throws IOException {
+ private static ByteBuffer dataBufferFromResource(String name) throws IOException {
ClassLoader loader = TestDump.class.getClassLoader();
InputStream is = loader.getResourceAsStream(name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -81,7 +82,7 @@
while ((read = is.read(buf)) != -1) {
baos.write(buf, 0, read);
}
- return new InMemoryBuffer(baos.toByteArray());
+ return ByteBuffer.wrap(baos.toByteArray());
}
/**
@@ -116,17 +117,24 @@
}
}
- DataBuffer hprof = dataBufferFromResource(mHprofResource);
- mSnapshot = AhatSnapshot.fromDataBuffer(hprof, map);
- mMain = findClass(mSnapshot, "Main");
- assert(mMain != null);
+ try {
+ ByteBuffer hprof = dataBufferFromResource(mHprofResource);
+ mSnapshot = Parser.parseHeapDump(hprof, map);
+ mMain = findClass(mSnapshot, "Main");
+ assert(mMain != null);
+ } catch (HprofFormatException e) {
+ throw new IOException("Unable to parse heap dump", e);
+ }
if (mHprofBaseResource != null) {
- DataBuffer hprofBase = dataBufferFromResource(mHprofBaseResource);
- mBaseline = AhatSnapshot.fromDataBuffer(hprofBase, map);
- mBaselineMain = findClass(mBaseline, "Main");
- assert(mBaselineMain != null);
-
+ try {
+ ByteBuffer hprofBase = dataBufferFromResource(mHprofBaseResource);
+ mBaseline = Parser.parseHeapDump(hprofBase, map);
+ mBaselineMain = findClass(mBaseline, "Main");
+ assert(mBaselineMain != null);
+ } catch (HprofFormatException e) {
+ throw new IOException("Unable to parse base heap dump", e);
+ }
Diff.snapshots(mSnapshot, mBaseline);
}
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index cd33a90..61d0035 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -30,6 +30,7 @@
"com.android.ahat.ObjectHandlerTest",
"com.android.ahat.OverviewHandlerTest",
"com.android.ahat.PerformanceTest",
+ "com.android.ahat.ProguardMapTest",
"com.android.ahat.RootedHandlerTest",
"com.android.ahat.QueryTest",
"com.android.ahat.SiteHandlerTest",