summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Richard Uhler <ruhler@google.com> 2015-09-10 16:45:54 -0700
committer Richard Uhler <ruhler@google.com> 2015-10-01 10:24:03 -0700
commit3524472e8f064dbd42e1a35d511a1efe36d2cbf2 (patch)
tree720ff24091021c6c3c6befc3b631d644df5144e9
parentd0d11f20811f260453f6dfe2e26d7dbd6ed55f01 (diff)
Generate an hprof file to test ahat.
This change sets up the infrastructure to write test cases for ahat that make use of an hprof file automatically generated from a sample program. Change-Id: Id11f656afb69c96a26655cc4caeb745ad844f431
-rw-r--r--tools/ahat/Android.mk44
-rw-r--r--tools/ahat/src/AhatSnapshot.java27
-rw-r--r--tools/ahat/src/InstanceUtils.java4
-rw-r--r--tools/ahat/src/Main.java15
-rw-r--r--tools/ahat/test-dump/Main.java52
-rw-r--r--tools/ahat/test/InstanceUtilsTest.java48
-rw-r--r--tools/ahat/test/TestDump.java89
-rw-r--r--tools/ahat/test/Tests.java1
8 files changed, 261 insertions, 19 deletions
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
index 3c1522c3d8..71366c1313 100644
--- a/tools/ahat/Android.mk
+++ b/tools/ahat/Android.mk
@@ -16,6 +16,8 @@
LOCAL_PATH := $(call my-dir)
+include art/build/Android.common_test.mk
+
# --- ahat.jar ----------------
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -44,7 +46,7 @@ $(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/ahat $(ACP)
ahat: $(LOCAL_BUILT_MODULE)
-# --- ahat-test.jar --------------
+# --- ahat-tests.jar --------------
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, test)
LOCAL_JAR_MANIFEST := test/manifest.txt
@@ -53,6 +55,42 @@ LOCAL_IS_HOST_MODULE := true
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE := ahat-tests
include $(BUILD_HOST_JAVA_LIBRARY)
+AHAT_TEST_JAR := $(LOCAL_BUILT_MODULE)
+
+# --- ahat-test-dump.jar --------------
+include $(CLEAR_VARS)
+LOCAL_MODULE := ahat-test-dump
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under, test-dump)
+include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
+
+# Determine the location of the test-dump.jar and test-dump.hprof files.
+# These use variables set implicitly by the include of
+# BUILD_HOST_DALVIK_JAVA_LIBRARY above.
+AHAT_TEST_DUMP_JAR := $(LOCAL_BUILT_MODULE)
+AHAT_TEST_DUMP_HPROF := $(intermediates.COMMON)/test-dump.hprof
+
+# Run ahat-test-dump.jar to generate test-dump.hprof
+AHAT_TEST_DUMP_DEPENDENCIES := \
+ $(ART_HOST_EXECUTABLES) \
+ $(HOST_OUT_EXECUTABLES)/art \
+ $(HOST_CORE_IMG_OUT_BASE)$(CORE_IMG_SUFFIX)
+
+$(AHAT_TEST_DUMP_HPROF): PRIVATE_AHAT_TEST_ART := $(HOST_OUT_EXECUTABLES)/art
+$(AHAT_TEST_DUMP_HPROF): PRIVATE_AHAT_TEST_DUMP_JAR := $(AHAT_TEST_DUMP_JAR)
+$(AHAT_TEST_DUMP_HPROF): PRIVATE_AHAT_TEST_DUMP_DEPENDENCIES := $(AHAT_TEST_DUMP_DEPENDENCIES)
+$(AHAT_TEST_DUMP_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIES)
+ $(PRIVATE_AHAT_TEST_ART) -cp $(PRIVATE_AHAT_TEST_DUMP_JAR) Main $@
+
+.PHONY: ahat-test
+ahat-test: PRIVATE_AHAT_TEST_DUMP_HPROF := $(AHAT_TEST_DUMP_HPROF)
+ahat-test: PRIVATE_AHAT_TEST_JAR := $(AHAT_TEST_JAR)
+ahat-test: $(AHAT_TEST_JAR) $(AHAT_TEST_DUMP_HPROF)
+ java -Dahat.test.dump.hprof=$(PRIVATE_AHAT_TEST_DUMP_HPROF) -jar $(PRIVATE_AHAT_TEST_JAR)
+
+# Clean up local variables.
+AHAT_TEST_DUMP_DEPENDENCIES :=
+AHAT_TEST_DUMP_HPROF :=
+AHAT_TEST_DUMP_JAR :=
+AHAT_TEST_JAR :=
-ahat-test: $(LOCAL_BUILT_MODULE)
- java -jar $<
diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java
index 2437d0388c..3035ef75c9 100644
--- a/tools/ahat/src/AhatSnapshot.java
+++ b/tools/ahat/src/AhatSnapshot.java
@@ -18,13 +18,18 @@ package com.android.ahat;
import com.android.tools.perflib.heap.ClassObj;
import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.HprofParser;
import com.android.tools.perflib.heap.Instance;
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 com.android.tools.perflib.heap.io.HprofBuffer;
+import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -47,7 +52,22 @@ class AhatSnapshot {
private Site mRootSite;
private Map<Heap, Long> mHeapSizes;
- public AhatSnapshot(Snapshot snapshot) {
+ /**
+ * Create an AhatSnapshot from an hprof file.
+ */
+ public static AhatSnapshot fromHprof(File hprof) throws IOException {
+ HprofBuffer buffer = new MemoryMappedFileBuffer(hprof);
+ Snapshot snapshot = (new HprofParser(buffer)).parse();
+ snapshot.computeDominators();
+ return new AhatSnapshot(snapshot);
+ }
+
+ /**
+ * Construct an AhatSnapshot for the given perflib snapshot.
+ * Ther user is responsible for calling snapshot.computeDominators before
+ * calling this AhatSnapshot constructor.
+ */
+ private AhatSnapshot(Snapshot snapshot) {
mSnapshot = snapshot;
mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps());
mDominated = new HashMap<Instance, List<Instance>>();
@@ -92,6 +112,11 @@ class AhatSnapshot {
}
}
+ // Note: This method is exposed for testing purposes.
+ public ClassObj findClass(String name) {
+ return mSnapshot.findClass(name);
+ }
+
public Instance findInstance(long id) {
return mSnapshot.findInstance(id);
}
diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java
index 7ee3ff24ed..a6ac3b8765 100644
--- a/tools/ahat/src/InstanceUtils.java
+++ b/tools/ahat/src/InstanceUtils.java
@@ -32,7 +32,7 @@ class InstanceUtils {
* given name.
*/
public static boolean isInstanceOfClass(Instance inst, String className) {
- ClassObj cls = inst.getClassObj();
+ ClassObj cls = (inst == null) ? null : inst.getClassObj();
return (cls != null && className.equals(cls.getClassName()));
}
@@ -132,7 +132,7 @@ class InstanceUtils {
* Read a field of an instance.
* Returns null if the field value is null or if the field couldn't be read.
*/
- private static Object getField(Instance inst, String fieldName) {
+ public static Object getField(Instance inst, String fieldName) {
if (!(inst instanceof ClassInstance)) {
return null;
}
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
index 2e2ddd299f..1563aa0262 100644
--- a/tools/ahat/src/Main.java
+++ b/tools/ahat/src/Main.java
@@ -16,10 +16,6 @@
package com.android.ahat;
-import com.android.tools.perflib.heap.HprofParser;
-import com.android.tools.perflib.heap.Snapshot;
-import com.android.tools.perflib.heap.io.HprofBuffer;
-import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
import com.sun.net.httpserver.HttpServer;
import java.io.File;
import java.io.IOException;
@@ -71,15 +67,8 @@ public class Main {
return;
}
- System.out.println("Reading hprof file...");
- HprofBuffer buffer = new MemoryMappedFileBuffer(hprof);
- Snapshot snapshot = (new HprofParser(buffer)).parse();
-
- System.out.println("Computing Dominators...");
- snapshot.computeDominators();
-
- System.out.println("Processing snapshot for ahat...");
- AhatSnapshot ahat = new AhatSnapshot(snapshot);
+ System.out.println("Processing hprof file...");
+ AhatSnapshot ahat = AhatSnapshot.fromHprof(hprof);
InetAddress loopback = InetAddress.getLoopbackAddress();
InetSocketAddress addr = new InetSocketAddress(loopback, port);
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
new file mode 100644
index 0000000000..cea1dc179e
--- /dev/null
+++ b/tools/ahat/test-dump/Main.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+import dalvik.system.VMDebug;
+import java.io.IOException;
+
+/**
+ * Program used to create a heap dump for test purposes.
+ */
+public class Main {
+ // Keep a reference to the DumpedStuff instance so that it is not garbage
+ // collected before we take the heap dump.
+ public static DumpedStuff stuff;
+
+ // We will take a heap dump that includes a single instance of this
+ // DumpedStuff class. Objects stored as fields in this class can be easily
+ // found in the hprof dump by searching for the instance of the DumpedStuff
+ // class and reading the desired field.
+ public static class DumpedStuff {
+ public String basicString = "hello, world";
+ public String nullString = null;
+ public Object anObject = new Object();
+ }
+
+ public static void main(String[] args) throws IOException {
+ if (args.length < 1) {
+ System.err.println("no output file specified");
+ return;
+ }
+ String file = args[0];
+
+ // Allocate the instance of DumpedStuff.
+ stuff = new DumpedStuff();
+
+ // Take a heap dump that will include that instance of DumpedStuff.
+ System.err.println("Dumping hprof data to " + file);
+ VMDebug.dumpHprofData(file);
+ }
+}
diff --git a/tools/ahat/test/InstanceUtilsTest.java b/tools/ahat/test/InstanceUtilsTest.java
new file mode 100644
index 0000000000..7613df4994
--- /dev/null
+++ b/tools/ahat/test/InstanceUtilsTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.tools.perflib.heap.Instance;
+import java.io.IOException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import org.junit.Test;
+
+public class InstanceUtilsTest {
+ @Test
+ public void basicString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("basicString");
+ assertEquals("hello, world", InstanceUtils.asString(str));
+ }
+
+ @Test
+ public void nullString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance obj = (Instance)dump.getDumpedThing("nullString");
+ assertNull(InstanceUtils.asString(obj));
+ }
+
+ @Test
+ public void notString() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance obj = (Instance)dump.getDumpedThing("anObject");
+ assertNotNull(obj);
+ assertNull(InstanceUtils.asString(obj));
+ }
+}
diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java
new file mode 100644
index 0000000000..c3a76e4f18
--- /dev/null
+++ b/tools/ahat/test/TestDump.java
@@ -0,0 +1,89 @@
+/*
+ * 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.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Field;
+import com.android.tools.perflib.heap.Instance;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * The TestDump class is used to get an AhatSnapshot for the test-dump
+ * program.
+ */
+public class TestDump {
+ // It can take on the order of a second to parse and process the test-dump
+ // hprof. To avoid repeating this overhead for each test case, we cache the
+ // loaded instance of TestDump and reuse it when possible. In theory the
+ // test cases should not be able to modify the cached snapshot in a way that
+ // is visible to other test cases.
+ private static TestDump mCachedTestDump = null;
+
+ private AhatSnapshot mSnapshot = null;
+
+ /**
+ * Load the test-dump.hprof file.
+ * The location of the file is read from the system property
+ * "ahat.test.dump.hprof", which is expected to be set on the command line.
+ * For example:
+ * java -Dahat.test.dump.hprof=test-dump.hprof -jar ahat-tests.jar
+ *
+ * An IOException is thrown if there is a failure reading the hprof file.
+ */
+ private TestDump() throws IOException {
+ String hprof = System.getProperty("ahat.test.dump.hprof");
+ mSnapshot = AhatSnapshot.fromHprof(new File(hprof));
+ }
+
+ /**
+ * Get the AhatSnapshot for the test dump program.
+ */
+ public AhatSnapshot getAhatSnapshot() {
+ return mSnapshot;
+ }
+
+ /**
+ * Return the value of a field in the DumpedStuff instance in the
+ * snapshot for the test-dump program.
+ */
+ public Object getDumpedThing(String name) {
+ ClassObj main = mSnapshot.findClass("Main");
+ Instance stuff = null;
+ for (Map.Entry<Field, Object> fields : main.getStaticFieldValues().entrySet()) {
+ if ("stuff".equals(fields.getKey().getName())) {
+ stuff = (Instance) fields.getValue();
+ }
+ }
+ return InstanceUtils.getField(stuff, name);
+ }
+
+ /**
+ * Get the test dump.
+ * An IOException is thrown if there is an error reading the test dump hprof
+ * file.
+ * To improve performance, this returns a cached instance of the TestDump
+ * when possible.
+ */
+ public static synchronized TestDump getTestDump() throws IOException {
+ if (mCachedTestDump == null) {
+ mCachedTestDump = new TestDump();
+ }
+ return mCachedTestDump;
+ }
+}
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index fb53d90801..bab712199c 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -22,6 +22,7 @@ public class Tests {
public static void main(String[] args) {
if (args.length == 0) {
args = new String[]{
+ "com.android.ahat.InstanceUtilsTest",
"com.android.ahat.QueryTest",
"com.android.ahat.SortTest"
};