414 lines
15 KiB
Java
414 lines
15 KiB
Java
/*
|
|
* 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.AhatClassObj;
|
|
import com.android.ahat.heapdump.AhatHeap;
|
|
import com.android.ahat.heapdump.AhatInstance;
|
|
import com.android.ahat.heapdump.AhatSnapshot;
|
|
import com.android.ahat.heapdump.PathElement;
|
|
import com.android.ahat.heapdump.Size;
|
|
import com.android.ahat.heapdump.Value;
|
|
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;
|
|
import static org.junit.Assert.assertFalse;
|
|
import static org.junit.Assert.assertNotNull;
|
|
import static org.junit.Assert.assertNull;
|
|
import static org.junit.Assert.assertTrue;
|
|
|
|
public class InstanceTest {
|
|
@Test
|
|
public void asStringBasic() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("basicString");
|
|
assertEquals("hello, world", str.asString());
|
|
}
|
|
|
|
@Test
|
|
public void asStringNonAscii() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
|
|
assertEquals("Sigma (Ʃ) is not ASCII", str.asString());
|
|
}
|
|
|
|
@Test
|
|
public void asStringEmbeddedZero() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
|
|
assertEquals("embedded\0...", str.asString());
|
|
}
|
|
|
|
@Test
|
|
public void asStringCharArray() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("charArray");
|
|
assertEquals("char thing", str.asString());
|
|
}
|
|
|
|
@Test
|
|
public void asStringTruncated() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("basicString");
|
|
assertEquals("hello", str.asString(5));
|
|
}
|
|
|
|
@Test
|
|
public void asStringTruncatedNonAscii() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
|
|
assertEquals("Sigma (Ʃ)", str.asString(9));
|
|
}
|
|
|
|
@Test
|
|
public void asStringTruncatedEmbeddedZero() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
|
|
assertEquals("embed", str.asString(5));
|
|
}
|
|
|
|
@Test
|
|
public void asStringCharArrayTruncated() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("charArray");
|
|
assertEquals("char ", str.asString(5));
|
|
}
|
|
|
|
@Test
|
|
public void asStringExactMax() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("basicString");
|
|
assertEquals("hello, world", str.asString(12));
|
|
}
|
|
|
|
@Test
|
|
public void asStringExactMaxNonAscii() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
|
|
assertEquals("Sigma (Ʃ) is not ASCII", str.asString(22));
|
|
}
|
|
|
|
@Test
|
|
public void asStringExactMaxEmbeddedZero() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
|
|
assertEquals("embedded\0...", str.asString(12));
|
|
}
|
|
|
|
@Test
|
|
public void asStringCharArrayExactMax() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("charArray");
|
|
assertEquals("char thing", str.asString(10));
|
|
}
|
|
|
|
@Test
|
|
public void asStringNotTruncated() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("basicString");
|
|
assertEquals("hello, world", str.asString(50));
|
|
}
|
|
|
|
@Test
|
|
public void asStringNotTruncatedNonAscii() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
|
|
assertEquals("Sigma (Ʃ) is not ASCII", str.asString(50));
|
|
}
|
|
|
|
@Test
|
|
public void asStringNotTruncatedEmbeddedZero() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
|
|
assertEquals("embedded\0...", str.asString(50));
|
|
}
|
|
|
|
@Test
|
|
public void asStringCharArrayNotTruncated() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("charArray");
|
|
assertEquals("char thing", str.asString(50));
|
|
}
|
|
|
|
@Test
|
|
public void asStringNegativeMax() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("basicString");
|
|
assertEquals("hello, world", str.asString(-3));
|
|
}
|
|
|
|
@Test
|
|
public void asStringNegativeMaxNonAscii() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
|
|
assertEquals("Sigma (Ʃ) is not ASCII", str.asString(-3));
|
|
}
|
|
|
|
@Test
|
|
public void asStringNegativeMaxEmbeddedZero() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
|
|
assertEquals("embedded\0...", str.asString(-3));
|
|
}
|
|
|
|
@Test
|
|
public void asStringCharArrayNegativeMax() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance str = dump.getDumpedAhatInstance("charArray");
|
|
assertEquals("char thing", str.asString(-3));
|
|
}
|
|
|
|
@Test
|
|
public void asStringNull() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance obj = dump.getDumpedAhatInstance("nullString");
|
|
assertNull(obj);
|
|
}
|
|
|
|
@Test
|
|
public void asStringNotString() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance obj = dump.getDumpedAhatInstance("anObject");
|
|
assertNotNull(obj);
|
|
assertNull(obj.asString());
|
|
}
|
|
|
|
@Test
|
|
public void basicReference() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
|
|
AhatInstance pref = dump.getDumpedAhatInstance("aPhantomReference");
|
|
AhatInstance wref = dump.getDumpedAhatInstance("aWeakReference");
|
|
AhatInstance nref = dump.getDumpedAhatInstance("aNullReferentReference");
|
|
AhatInstance referent = dump.getDumpedAhatInstance("anObject");
|
|
assertNotNull(pref);
|
|
assertNotNull(wref);
|
|
assertNotNull(nref);
|
|
assertNotNull(referent);
|
|
assertEquals(referent, pref.getReferent());
|
|
assertEquals(referent, wref.getReferent());
|
|
assertNull(nref.getReferent());
|
|
assertNull(referent.getReferent());
|
|
}
|
|
|
|
@Test
|
|
public void unreachableReferent() throws IOException {
|
|
// The test dump program should never be under enough GC pressure for the
|
|
// soft reference to be cleared. Ensure that ahat will show the soft
|
|
// reference as having a non-null referent.
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance ref = dump.getDumpedAhatInstance("aSoftReference");
|
|
assertNotNull(ref.getReferent());
|
|
}
|
|
|
|
@Test
|
|
public void gcRootPath() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
|
|
AhatClassObj main = dump.getAhatSnapshot().findClass("Main");
|
|
AhatInstance gcPathArray = dump.getDumpedAhatInstance("gcPathArray");
|
|
Value value = gcPathArray.asArrayInstance().getValue(2);
|
|
AhatInstance base = value.asAhatInstance();
|
|
AhatInstance left = base.getRefField("left");
|
|
AhatInstance right = base.getRefField("right");
|
|
AhatInstance target = left.getRefField("right");
|
|
|
|
List<PathElement> path = target.getPathFromGcRoot();
|
|
assertEquals(6, path.size());
|
|
|
|
assertEquals(main, path.get(0).instance);
|
|
assertEquals(".stuff", path.get(0).field);
|
|
assertTrue(path.get(0).isDominator);
|
|
|
|
assertEquals(".gcPathArray", path.get(1).field);
|
|
assertTrue(path.get(1).isDominator);
|
|
|
|
assertEquals(gcPathArray, path.get(2).instance);
|
|
assertEquals("[2]", path.get(2).field);
|
|
assertTrue(path.get(2).isDominator);
|
|
|
|
assertEquals(base, path.get(3).instance);
|
|
assertTrue(path.get(3).isDominator);
|
|
|
|
// There are two possible paths. Either it can go through the 'left' node,
|
|
// or the 'right' node.
|
|
if (path.get(3).field.equals(".left")) {
|
|
assertEquals(".left", path.get(3).field);
|
|
|
|
assertEquals(left, path.get(4).instance);
|
|
assertEquals(".right", path.get(4).field);
|
|
assertFalse(path.get(4).isDominator);
|
|
|
|
} else {
|
|
assertEquals(".right", path.get(3).field);
|
|
|
|
assertEquals(right, path.get(4).instance);
|
|
assertEquals(".left", path.get(4).field);
|
|
assertFalse(path.get(4).isDominator);
|
|
}
|
|
|
|
assertEquals(target, path.get(5).instance);
|
|
assertEquals("", path.get(5).field);
|
|
assertTrue(path.get(5).isDominator);
|
|
}
|
|
|
|
@Test
|
|
public void retainedSize() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
|
|
// anObject should not be an immediate dominator of any other object. This
|
|
// means its retained size should be equal to its size for the heap it was
|
|
// allocated on, and should be 0 for all other heaps.
|
|
AhatInstance anObject = dump.getDumpedAhatInstance("anObject");
|
|
AhatSnapshot snapshot = dump.getAhatSnapshot();
|
|
Size size = anObject.getSize();
|
|
assertEquals(size, anObject.getTotalRetainedSize());
|
|
assertEquals(size, anObject.getRetainedSize(anObject.getHeap()));
|
|
for (AhatHeap heap : snapshot.getHeaps()) {
|
|
if (!heap.equals(anObject.getHeap())) {
|
|
assertEquals(String.format("For heap '%s'", heap.getName()),
|
|
Size.ZERO, anObject.getRetainedSize(heap));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void objectNotABitmap() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance obj = dump.getDumpedAhatInstance("anObject");
|
|
assertNull(obj.asBitmap());
|
|
}
|
|
|
|
@Test
|
|
public void arrayNotABitmap() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
|
|
assertNull(obj.asBitmap());
|
|
}
|
|
|
|
@Test
|
|
public void classObjNotABitmap() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
|
|
assertNull(obj.asBitmap());
|
|
}
|
|
|
|
@Test
|
|
public void classInstanceToString() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance obj = dump.getDumpedAhatInstance("aPhantomReference");
|
|
long id = obj.getId();
|
|
assertEquals(String.format("java.lang.ref.PhantomReference@%08x", id), obj.toString());
|
|
}
|
|
|
|
@Test
|
|
public void classObjToString() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
|
|
assertEquals("Main", obj.toString());
|
|
}
|
|
|
|
@Test
|
|
public void arrayInstanceToString() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
|
|
long id = obj.getId();
|
|
|
|
// There's a bug in perfib's proguard deobfuscation for arrays.
|
|
// To work around that bug for the time being, only test the suffix of
|
|
// the toString result. Ideally we test for string equality against
|
|
// "Main$ObjectTree[4]@%08x", id.
|
|
assertTrue(obj.toString().endsWith(String.format("[4]@%08x", id)));
|
|
}
|
|
|
|
@Test
|
|
public void primArrayInstanceToString() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance obj = dump.getDumpedAhatInstance("bigArray");
|
|
long id = obj.getId();
|
|
assertEquals(String.format("byte[1000000]@%08x", id), obj.toString());
|
|
}
|
|
|
|
@Test
|
|
public void isNotRoot() throws IOException {
|
|
TestDump dump = TestDump.getTestDump();
|
|
AhatInstance obj = dump.getDumpedAhatInstance("anObject");
|
|
assertFalse(obj.isRoot());
|
|
assertNull(obj.getRootTypes());
|
|
}
|
|
|
|
@Test
|
|
public void asStringEmbedded() throws IOException {
|
|
// Set up a heap dump with an instance of java.lang.String of
|
|
// "hello" with instance id 0x42 that is backed by a char array that is
|
|
// bigger. This is how ART used to represent strings, and we should still
|
|
// support it in case the heap dump is from a previous platform version.
|
|
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);
|
|
AhatInstance chars = snapshot.findInstance(0x41);
|
|
assertNotNull(chars);
|
|
assertEquals("not helloop", chars.asString());
|
|
|
|
AhatInstance stringInstance = snapshot.findInstance(0x42);
|
|
assertNotNull(stringInstance);
|
|
assertEquals("hello", stringInstance.asString());
|
|
}
|
|
}
|