blob: e94d1a97691086ffed66bedf5cc539409caf31bb [file] [log] [blame]
/*
* 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.AhatInstance;
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.Reachability;
import com.android.ahat.heapdump.Site;
import com.android.ahat.heapdump.Value;
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;
import java.util.List;
import java.util.Objects;
/**
* The TestDump class is used to get the current and baseline AhatSnapshots
* for heap dumps generated by the test-dump program that are stored as
* resources in this jar file.
*/
public class TestDump {
// It can take on the order of a second to parse and process test dumps.
// To avoid repeating this overhead for each test case, we provide a way to
// cache 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 List<TestDump> mCachedTestDumps = new ArrayList<TestDump>();
// The name of the resources this test dump is loaded from.
private String mHprofResource;
private String mHprofBaseResource;
private String mMapResource;
private Reachability mRetained;
// If the test dump fails to load the first time, it will likely fail every
// other test we try. Rather than having to wait a potentially very long
// time for test dump loading to fail over and over again, record when it
// fails and don't try to load it again.
private boolean mTestDumpFailed = true;
// The loaded heap dumps.
private AhatSnapshot mSnapshot;
private AhatSnapshot mBaseline;
// Cached reference to the 'Main' class object in the snapshot and baseline
// heap dumps.
private AhatClassObj mMain;
private AhatClassObj mBaselineMain;
/**
* Read the named resource into a ByteBuffer.
*/
private static ByteBuffer dataBufferFromResource(String name) throws IOException {
ClassLoader loader = TestDump.class.getClassLoader();
InputStream is = loader.getResourceAsStream(name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int read;
while ((read = is.read(buf)) != -1) {
baos.write(buf, 0, read);
}
return ByteBuffer.wrap(baos.toByteArray());
}
/**
* Create a TestDump instance.
* The load() method should be called to load and process the heap dumps.
* The files are specified as names of resources compiled into the jar file.
* The baseline resouce may be null to indicate that no diffing should be
* performed.
* The map resource may be null to indicate no proguard map will be used.
*
*/
private TestDump(String hprofResource,
String hprofBaseResource,
String mapResource,
Reachability retained) {
mHprofResource = hprofResource;
mHprofBaseResource = hprofBaseResource;
mMapResource = mapResource;
mRetained = retained;
}
/**
* Load the heap dumps for this TestDump.
* An IOException is thrown if there is a failure reading the hprof files or
* the proguard map.
*/
private void load() throws IOException {
ProguardMap map = new ProguardMap();
if (mMapResource != null) {
try {
ClassLoader loader = TestDump.class.getClassLoader();
InputStream is = loader.getResourceAsStream(mMapResource);
map.readFromReader(new InputStreamReader(is));
} catch (ParseException e) {
throw new IOException("Unable to load proguard map", e);
}
}
try {
ByteBuffer hprof = dataBufferFromResource(mHprofResource);
mSnapshot = new Parser(hprof).map(map).retained(mRetained).parse();
mMain = findClass(mSnapshot, "Main");
assert(mMain != null);
} catch (HprofFormatException e) {
throw new IOException("Unable to parse heap dump", e);
}
if (mHprofBaseResource != null) {
try {
ByteBuffer hprofBase = dataBufferFromResource(mHprofBaseResource);
mBaseline = new Parser(hprofBase).map(map).retained(mRetained).parse();
mBaselineMain = findClass(mBaseline, "Main");
assert(mBaselineMain != null);
} catch (HprofFormatException e) {
throw new IOException("Unable to parse base heap dump", e);
}
Diff.snapshots(mSnapshot, mBaseline);
}
mTestDumpFailed = false;
}
/**
* Get the AhatSnapshot for the test dump program.
*/
public AhatSnapshot getAhatSnapshot() {
return mSnapshot;
}
/**
* Get the baseline AhatSnapshot for the test dump program.
*/
public AhatSnapshot getBaselineAhatSnapshot() {
return mBaseline;
}
/**
* Returns the value of a field in the DumpedStuff instance in the
* snapshot for the test-dump program.
*/
public Value getDumpedValue(String name) {
return getDumpedValue(name, mMain);
}
/**
* Returns the value of a field in the DumpedStuff instance in the
* baseline snapshot for the test-dump program.
*/
public Value getBaselineDumpedValue(String name) {
return getDumpedValue(name, mBaselineMain);
}
/**
* Returns the value of a field in the DumpedStuff instance given the Main
* class object for the snapshot.
*/
private static Value getDumpedValue(String name, AhatClassObj main) {
AhatInstance stuff = null;
for (FieldValue field : main.getStaticFieldValues()) {
if ("stuff".equals(field.name)) {
stuff = field.value.asAhatInstance();
}
}
return stuff.getField(name);
}
/**
* Returns a class object in the given heap dump whose name matches the
* given name, or null if no such class object could be found.
*/
private static AhatClassObj findClass(AhatSnapshot snapshot, String name) {
Site root = snapshot.getRootSite();
Collection<AhatInstance> classes = new ArrayList<AhatInstance>();
root.getObjects(null, "java.lang.Class", classes);
for (AhatInstance inst : classes) {
if (inst.isClassObj()) {
AhatClassObj cls = inst.asClassObj();
if (name.equals(cls.getName())) {
return cls;
}
}
}
return null;
}
/**
* Returns a class object in the heap dump whose name matches the given
* name, or null if no such class object could be found.
*/
public AhatClassObj findClass(String name) {
return findClass(mSnapshot, name);
}
/**
* Returns the value of a non-primitive field in the DumpedStuff instance in
* the snapshot for the test-dump program.
*/
public AhatInstance getDumpedAhatInstance(String name) {
Value value = getDumpedValue(name);
return value == null ? null : value.asAhatInstance();
}
/**
* Returns the value of a non-primitive field in the DumpedStuff instance in
* the baseline snapshot for the test-dump program.
*/
public AhatInstance getBaselineDumpedAhatInstance(String name) {
Value value = getBaselineDumpedValue(name);
return value == null ? null : value.asAhatInstance();
}
/**
* Get the default (cached) 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 {
return getTestDump("test-dump.hprof",
"test-dump-base.hprof",
"test-dump.map",
Reachability.STRONG);
}
/**
* Get a (cached) test dump.
* @param hprof - The string resouce name of the hprof file.
* @param base - The string resouce name of the baseline hprof, may be null.
* @param map - The string resouce name of the proguard map, may be null.
* @param retained the weakest reachability of instances to treat as retained.
* 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(String hprof,
String base,
String map,
Reachability retained)
throws IOException {
for (TestDump loaded : mCachedTestDumps) {
if (Objects.equals(loaded.mHprofResource, hprof)
&& Objects.equals(loaded.mHprofBaseResource, base)
&& Objects.equals(loaded.mMapResource, map)
&& Objects.equals(loaded.mRetained, retained)) {
if (loaded.mTestDumpFailed) {
throw new IOException("Test dump failed before, assuming it will again");
}
return loaded;
}
}
TestDump dump = new TestDump(hprof, base, map, retained);
mCachedTestDumps.add(dump);
dump.load();
return dump;
}
}