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