InstanceUtils.java revision c7f7712a3808b8e3046eae8a4dbb7f6a7fc6faf2
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.tools.perflib.heap.ArrayInstance;
20import com.android.tools.perflib.heap.ClassInstance;
21import com.android.tools.perflib.heap.ClassObj;
22import com.android.tools.perflib.heap.Instance;
23import com.android.tools.perflib.heap.Heap;
24import com.android.tools.perflib.heap.Type;
25import java.awt.image.BufferedImage;
26
27/**
28 * Utilities for extracting information from hprof instances.
29 */
30class InstanceUtils {
31  /**
32   * Returns true if the given instance is an instance of a class with the
33   * given name.
34   */
35  private static boolean isInstanceOfClass(Instance inst, String className) {
36    ClassObj cls = (inst == null) ? null : inst.getClassObj();
37    return (cls != null && className.equals(cls.getClassName()));
38  }
39
40  /**
41   * Read the byte[] value from an hprof Instance.
42   * Returns null if the instance is not a byte array.
43   */
44  private static byte[] asByteArray(Instance inst) {
45    if (! (inst instanceof ArrayInstance)) {
46      return null;
47    }
48
49    ArrayInstance array = (ArrayInstance)inst;
50    if (array.getArrayType() != Type.BYTE) {
51      return null;
52    }
53
54    Object[] objs = array.getValues();
55    byte[] bytes = new byte[objs.length];
56    for (int i = 0; i < objs.length; i++) {
57      Byte b = (Byte)objs[i];
58      bytes[i] = b.byteValue();
59    }
60    return bytes;
61  }
62
63
64  /**
65   * Read the string value from an hprof Instance.
66   * Returns null if the object can't be interpreted as a string.
67   */
68  public static String asString(Instance inst) {
69    return asString(inst, -1);
70  }
71
72  /**
73   * Read the string value from an hprof Instance.
74   * Returns null if the object can't be interpreted as a string.
75   * The returned string is truncated to maxChars characters.
76   * If maxChars is negative, the returned string is not truncated.
77   */
78  public static String asString(Instance inst, int maxChars) {
79    // The inst object could either be a java.lang.String or a char[]. If it
80    // is a char[], use that directly as the value, otherwise use the value
81    // field of the string object. The field accesses for count and offset
82    // later on will work okay regardless of what type the inst object is.
83    Object value = inst;
84    if (isInstanceOfClass(inst, "java.lang.String")) {
85      value = getField(inst, "value");
86    }
87
88    if (!(value instanceof ArrayInstance)) {
89      return null;
90    }
91
92    ArrayInstance chars = (ArrayInstance) value;
93    if (chars.getArrayType() != Type.CHAR) {
94      return null;
95    }
96
97    // TODO: When perflib provides a better way to get the length of the
98    // array, we should use that here.
99    int numChars = chars.getValues().length;
100    int count = getIntField(inst, "count", numChars);
101    if (count == 0) {
102      return "";
103    }
104    if (0 <= maxChars && maxChars < count) {
105      count = maxChars;
106    }
107
108    int offset = getIntField(inst, "offset", 0);
109    int end = offset + count - 1;
110    if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
111      return new String(chars.asCharArray(offset, count));
112    }
113    return null;
114  }
115
116  /**
117   * Read the bitmap data for the given android.graphics.Bitmap object.
118   * Returns null if the object isn't for android.graphics.Bitmap or the
119   * bitmap data couldn't be read.
120   */
121  public static BufferedImage asBitmap(Instance inst) {
122    if (!isInstanceOfClass(inst, "android.graphics.Bitmap")) {
123      return null;
124    }
125
126    Integer width = getIntField(inst, "mWidth", null);
127    if (width == null) {
128      return null;
129    }
130
131    Integer height = getIntField(inst, "mHeight", null);
132    if (height == null) {
133      return null;
134    }
135
136    byte[] buffer = getByteArrayField(inst, "mBuffer");
137    if (buffer == null) {
138      return null;
139    }
140
141    // Convert the raw data to an image
142    // Convert BGRA to ABGR
143    int[] abgr = new int[height * width];
144    for (int i = 0; i < abgr.length; i++) {
145      abgr[i] = (
146          (((int)buffer[i * 4 + 3] & 0xFF) << 24) +
147          (((int)buffer[i * 4 + 0] & 0xFF) << 16) +
148          (((int)buffer[i * 4 + 1] & 0xFF) << 8) +
149          ((int)buffer[i * 4 + 2] & 0xFF));
150    }
151
152    BufferedImage bitmap = new BufferedImage(
153        width, height, BufferedImage.TYPE_4BYTE_ABGR);
154    bitmap.setRGB(0, 0, width, height, abgr, 0, width);
155    return bitmap;
156  }
157
158  /**
159   * Read a field of an instance.
160   * Returns null if the field value is null or if the field couldn't be read.
161   */
162  public static Object getField(Instance inst, String fieldName) {
163    if (!(inst instanceof ClassInstance)) {
164      return null;
165    }
166
167    ClassInstance clsinst = (ClassInstance) inst;
168    Object value = null;
169    int count = 0;
170    for (ClassInstance.FieldValue field : clsinst.getValues()) {
171      if (fieldName.equals(field.getField().getName())) {
172        value = field.getValue();
173        count++;
174      }
175    }
176    return count == 1 ? value : null;
177  }
178
179  /**
180   * Read a reference field of an instance.
181   * Returns null if the field value is null, or if the field couldn't be read.
182   */
183  private static Instance getRefField(Instance inst, String fieldName) {
184    Object value = getField(inst, fieldName);
185    if (!(value instanceof Instance)) {
186      return null;
187    }
188    return (Instance)value;
189  }
190
191  /**
192   * Read an int field of an instance.
193   * The field is assumed to be an int type.
194   * Returns <code>def</code> if the field value is not an int or could not be
195   * read.
196   */
197  private static Integer getIntField(Instance inst, String fieldName, Integer def) {
198    Object value = getField(inst, fieldName);
199    if (!(value instanceof Integer)) {
200      return def;
201    }
202    return (Integer)value;
203  }
204
205  /**
206   * Read a long field of an instance.
207   * The field is assumed to be a long type.
208   * Returns <code>def</code> if the field value is not an long or could not
209   * be read.
210   */
211  private static Long getLongField(Instance inst, String fieldName, Long def) {
212    Object value = getField(inst, fieldName);
213    if (!(value instanceof Long)) {
214      return def;
215    }
216    return (Long)value;
217  }
218
219  /**
220   * Read the given field from the given instance.
221   * The field is assumed to be a byte[] field.
222   * Returns null if the field value is null, not a byte[] or could not be read.
223   */
224  private static byte[] getByteArrayField(Instance inst, String fieldName) {
225    Object value = getField(inst, fieldName);
226    if (!(value instanceof Instance)) {
227      return null;
228    }
229    return asByteArray((Instance)value);
230  }
231
232  // Return the bitmap instance associated with this object, or null if there
233  // is none. This works for android.graphics.Bitmap instances and their
234  // underlying Byte[] instances.
235  public static Instance getAssociatedBitmapInstance(Instance inst) {
236    ClassObj cls = inst.getClassObj();
237    if (cls == null) {
238      return null;
239    }
240
241    if ("android.graphics.Bitmap".equals(cls.getClassName())) {
242      return inst;
243    }
244
245    if (inst instanceof ArrayInstance) {
246      ArrayInstance array = (ArrayInstance)inst;
247      if (array.getArrayType() == Type.BYTE && inst.getHardReferences().size() == 1) {
248        Instance ref = inst.getHardReferences().get(0);
249        ClassObj clsref = ref.getClassObj();
250        if (clsref != null && "android.graphics.Bitmap".equals(clsref.getClassName())) {
251          return ref;
252        }
253      }
254    }
255    return null;
256  }
257
258  private static boolean isJavaLangRefReference(Instance inst) {
259    ClassObj cls = (inst == null) ? null : inst.getClassObj();
260    while (cls != null) {
261      if ("java.lang.ref.Reference".equals(cls.getClassName())) {
262        return true;
263      }
264      cls = cls.getSuperClassObj();
265    }
266    return false;
267  }
268
269  public static Instance getReferent(Instance inst) {
270    if (isJavaLangRefReference(inst)) {
271      return getRefField(inst, "referent");
272    }
273    return null;
274  }
275
276  /**
277   * Assuming inst represents a DexCache object, return the dex location for
278   * that dex cache. Returns null if the given instance doesn't represent a
279   * DexCache object or the location could not be found.
280   * If maxChars is non-negative, the returned location is truncated to
281   * maxChars in length.
282   */
283  public static String getDexCacheLocation(Instance inst, int maxChars) {
284    if (isInstanceOfClass(inst, "java.lang.DexCache")) {
285      Instance location = getRefField(inst, "location");
286      if (location != null) {
287        return asString(location, maxChars);
288      }
289    }
290    return null;
291  }
292
293  public static class NativeAllocation {
294    public long size;
295    public Heap heap;
296    public long pointer;
297    public Instance referent;
298
299    public NativeAllocation(long size, Heap heap, long pointer, Instance referent) {
300      this.size = size;
301      this.heap = heap;
302      this.pointer = pointer;
303      this.referent = referent;
304    }
305  }
306
307  /**
308   * Assuming inst represents a NativeAllocation, return information about the
309   * native allocation. Returns null if the given instance doesn't represent a
310   * native allocation.
311   */
312  public static NativeAllocation getNativeAllocation(Instance inst) {
313    if (!isInstanceOfClass(inst, "libcore.util.NativeAllocationRegistry$CleanerThunk")) {
314      return null;
315    }
316
317    Long pointer = InstanceUtils.getLongField(inst, "nativePtr", null);
318    if (pointer == null) {
319      return null;
320    }
321
322    // Search for the registry field of inst.
323    // Note: We know inst as an instance of ClassInstance because we already
324    // read the nativePtr field from it.
325    Instance registry = null;
326    for (ClassInstance.FieldValue field : ((ClassInstance)inst).getValues()) {
327      Object fieldValue = field.getValue();
328      if (fieldValue instanceof Instance) {
329        Instance fieldInst = (Instance)fieldValue;
330        if (isInstanceOfClass(fieldInst, "libcore.util.NativeAllocationRegistry")) {
331          registry = fieldInst;
332          break;
333        }
334      }
335    }
336
337    if (registry == null) {
338      return null;
339    }
340
341    Long size = InstanceUtils.getLongField(registry, "size", null);
342    if (size == null) {
343      return null;
344    }
345
346    Instance referent = null;
347    for (Instance ref : inst.getHardReferences()) {
348      if (isInstanceOfClass(ref, "sun.misc.Cleaner")) {
349        referent = InstanceUtils.getReferent(ref);
350        if (referent != null) {
351          break;
352        }
353      }
354    }
355
356    if (referent == null) {
357      return null;
358    }
359    return new NativeAllocation(size, inst.getHeap(), pointer, referent);
360  }
361}
362