1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.ahat; 18 19import com.android.ahat.heapdump.AhatClassObj; 20import com.android.ahat.heapdump.AhatInstance; 21import com.android.ahat.heapdump.AhatSnapshot; 22import com.android.ahat.heapdump.Diff; 23import com.android.ahat.heapdump.FieldValue; 24import com.android.ahat.heapdump.HprofFormatException; 25import com.android.ahat.heapdump.Parser; 26import com.android.ahat.heapdump.Site; 27import com.android.ahat.heapdump.Value; 28import com.android.ahat.proguard.ProguardMap; 29import java.io.ByteArrayOutputStream; 30import java.io.IOException; 31import java.io.InputStream; 32import java.io.InputStreamReader; 33import java.nio.ByteBuffer; 34import java.text.ParseException; 35import java.util.ArrayList; 36import java.util.Collection; 37import java.util.List; 38import java.util.Objects; 39 40/** 41 * The TestDump class is used to get the current and baseline AhatSnapshots 42 * for heap dumps generated by the test-dump program that are stored as 43 * resources in this jar file. 44 */ 45public class TestDump { 46 // It can take on the order of a second to parse and process test dumps. 47 // To avoid repeating this overhead for each test case, we provide a way to 48 // cache loaded instance of TestDump and reuse it when possible. In theory 49 // the test cases should not be able to modify the cached snapshot in a way 50 // that is visible to other test cases. 51 private static List<TestDump> mCachedTestDumps = new ArrayList<TestDump>(); 52 53 // The name of the resources this test dump is loaded from. 54 private String mHprofResource; 55 private String mHprofBaseResource; 56 private String mMapResource; 57 58 // If the test dump fails to load the first time, it will likely fail every 59 // other test we try. Rather than having to wait a potentially very long 60 // time for test dump loading to fail over and over again, record when it 61 // fails and don't try to load it again. 62 private boolean mTestDumpFailed = true; 63 64 // The loaded heap dumps. 65 private AhatSnapshot mSnapshot; 66 private AhatSnapshot mBaseline; 67 68 // Cached reference to the 'Main' class object in the snapshot and baseline 69 // heap dumps. 70 private AhatClassObj mMain; 71 private AhatClassObj mBaselineMain; 72 73 /** 74 * Read the named resource into a ByteBuffer. 75 */ 76 private static ByteBuffer dataBufferFromResource(String name) throws IOException { 77 ClassLoader loader = TestDump.class.getClassLoader(); 78 InputStream is = loader.getResourceAsStream(name); 79 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 80 byte[] buf = new byte[4096]; 81 int read; 82 while ((read = is.read(buf)) != -1) { 83 baos.write(buf, 0, read); 84 } 85 return ByteBuffer.wrap(baos.toByteArray()); 86 } 87 88 /** 89 * Create a TestDump instance. 90 * The load() method should be called to load and process the heap dumps. 91 * The files are specified as names of resources compiled into the jar file. 92 * The baseline resouce may be null to indicate that no diffing should be 93 * performed. 94 * The map resource may be null to indicate no proguard map will be used. 95 * 96 */ 97 private TestDump(String hprofResource, String hprofBaseResource, String mapResource) { 98 mHprofResource = hprofResource; 99 mHprofBaseResource = hprofBaseResource; 100 mMapResource = mapResource; 101 } 102 103 /** 104 * Load the heap dumps for this TestDump. 105 * An IOException is thrown if there is a failure reading the hprof files or 106 * the proguard map. 107 */ 108 private void load() throws IOException { 109 ProguardMap map = new ProguardMap(); 110 if (mMapResource != null) { 111 try { 112 ClassLoader loader = TestDump.class.getClassLoader(); 113 InputStream is = loader.getResourceAsStream(mMapResource); 114 map.readFromReader(new InputStreamReader(is)); 115 } catch (ParseException e) { 116 throw new IOException("Unable to load proguard map", e); 117 } 118 } 119 120 try { 121 ByteBuffer hprof = dataBufferFromResource(mHprofResource); 122 mSnapshot = Parser.parseHeapDump(hprof, map); 123 mMain = findClass(mSnapshot, "Main"); 124 assert(mMain != null); 125 } catch (HprofFormatException e) { 126 throw new IOException("Unable to parse heap dump", e); 127 } 128 129 if (mHprofBaseResource != null) { 130 try { 131 ByteBuffer hprofBase = dataBufferFromResource(mHprofBaseResource); 132 mBaseline = Parser.parseHeapDump(hprofBase, map); 133 mBaselineMain = findClass(mBaseline, "Main"); 134 assert(mBaselineMain != null); 135 } catch (HprofFormatException e) { 136 throw new IOException("Unable to parse base heap dump", e); 137 } 138 Diff.snapshots(mSnapshot, mBaseline); 139 } 140 141 mTestDumpFailed = false; 142 } 143 144 /** 145 * Get the AhatSnapshot for the test dump program. 146 */ 147 public AhatSnapshot getAhatSnapshot() { 148 return mSnapshot; 149 } 150 151 /** 152 * Get the baseline AhatSnapshot for the test dump program. 153 */ 154 public AhatSnapshot getBaselineAhatSnapshot() { 155 return mBaseline; 156 } 157 158 /** 159 * Returns the value of a field in the DumpedStuff instance in the 160 * snapshot for the test-dump program. 161 */ 162 public Value getDumpedValue(String name) { 163 return getDumpedValue(name, mMain); 164 } 165 166 /** 167 * Returns the value of a field in the DumpedStuff instance in the 168 * baseline snapshot for the test-dump program. 169 */ 170 public Value getBaselineDumpedValue(String name) { 171 return getDumpedValue(name, mBaselineMain); 172 } 173 174 /** 175 * Returns the value of a field in the DumpedStuff instance given the Main 176 * class object for the snapshot. 177 */ 178 private static Value getDumpedValue(String name, AhatClassObj main) { 179 AhatInstance stuff = null; 180 for (FieldValue field : main.getStaticFieldValues()) { 181 if ("stuff".equals(field.name)) { 182 stuff = field.value.asAhatInstance(); 183 } 184 } 185 return stuff.getField(name); 186 } 187 188 /** 189 * Returns a class object in the given heap dump whose name matches the 190 * given name, or null if no such class object could be found. 191 */ 192 private static AhatClassObj findClass(AhatSnapshot snapshot, String name) { 193 Site root = snapshot.getRootSite(); 194 Collection<AhatInstance> classes = new ArrayList<AhatInstance>(); 195 root.getObjects(null, "java.lang.Class", classes); 196 for (AhatInstance inst : classes) { 197 if (inst.isClassObj()) { 198 AhatClassObj cls = inst.asClassObj(); 199 if (name.equals(cls.getName())) { 200 return cls; 201 } 202 } 203 } 204 return null; 205 } 206 207 /** 208 * Returns a class object in the heap dump whose name matches the given 209 * name, or null if no such class object could be found. 210 */ 211 public AhatClassObj findClass(String name) { 212 return findClass(mSnapshot, name); 213 } 214 215 /** 216 * Returns the value of a non-primitive field in the DumpedStuff instance in 217 * the snapshot for the test-dump program. 218 */ 219 public AhatInstance getDumpedAhatInstance(String name) { 220 Value value = getDumpedValue(name); 221 return value == null ? null : value.asAhatInstance(); 222 } 223 224 /** 225 * Returns the value of a non-primitive field in the DumpedStuff instance in 226 * the baseline snapshot for the test-dump program. 227 */ 228 public AhatInstance getBaselineDumpedAhatInstance(String name) { 229 Value value = getBaselineDumpedValue(name); 230 return value == null ? null : value.asAhatInstance(); 231 } 232 233 /** 234 * Get the default (cached) test dump. 235 * An IOException is thrown if there is an error reading the test dump hprof 236 * file. 237 * To improve performance, this returns a cached instance of the TestDump 238 * when possible. 239 */ 240 public static synchronized TestDump getTestDump() throws IOException { 241 return getTestDump("test-dump.hprof", "test-dump-base.hprof", "test-dump.map"); 242 } 243 244 /** 245 * Get a (cached) test dump. 246 * @param hprof - The string resouce name of the hprof file. 247 * @param base - The string resouce name of the baseline hprof, may be null. 248 * @param map - The string resouce name of the proguard map, may be null. 249 * An IOException is thrown if there is an error reading the test dump hprof 250 * file. 251 * To improve performance, this returns a cached instance of the TestDump 252 * when possible. 253 */ 254 public static synchronized TestDump getTestDump(String hprof, String base, String map) 255 throws IOException { 256 for (TestDump loaded : mCachedTestDumps) { 257 if (Objects.equals(loaded.mHprofResource, hprof) 258 && Objects.equals(loaded.mHprofBaseResource, base) 259 && Objects.equals(loaded.mMapResource, map)) { 260 if (loaded.mTestDumpFailed) { 261 throw new IOException("Test dump failed before, assuming it will again"); 262 } 263 return loaded; 264 } 265 } 266 267 TestDump dump = new TestDump(hprof, base, map); 268 mCachedTestDumps.add(dump); 269 dump.load(); 270 return dump; 271 } 272} 273