| /* |
| * Copyright (C) 2008 Google Inc. |
| * |
| * 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.tools.perflib.heap; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| |
| import java.util.*; |
| |
| public class Queries { |
| /* |
| * NOTES: Here's a list of the queries that can be done in hat and |
| * how you'd perform a similar query here in hit: |
| * |
| * hat hit |
| * ------------------------------------------------------------------------ |
| * allClasses classes |
| * allClassesWithPlatform allClasses |
| * class findClass |
| * instances instancesOf |
| * allInstances allInstancesOf |
| * object findObject |
| * showRoots getRoots |
| * newInstances newInstances |
| * |
| * reachableFrom make a call to findObject to get the target |
| * parent object, this will give you an Instance. |
| * Then call visit(Set, Filter) on that to have |
| * it build the set of objects in its subgraph. |
| * |
| * rootsTo make a call to findObject on the leaf node |
| * in question, this will give you an Instance. |
| * Instances have an ArrayList of all of the |
| * parent objects that refer to it. You can |
| * follow those parent links until you hit an |
| * object whose parent is null or a ThreadObj. |
| * You've not successfully traced the paths to |
| * the roots. |
| */ |
| |
| private static final String DEFAULT_PACKAGE = "<default>"; |
| |
| /* |
| * Produce a collection of all classes, broken down by package. |
| * The keys of the resultant map iterate in sorted package order. |
| * The values of the map are the classes defined in each package. |
| */ |
| @NonNull |
| public static Map<String, Set<ClassObj>> allClasses(@NonNull Snapshot snapshot) { |
| return classes(snapshot, null); |
| } |
| |
| @NonNull |
| public static Map<String, Set<ClassObj>> classes(@NonNull Snapshot snapshot, |
| @Nullable String[] excludedPrefixes) { |
| TreeMap<String, Set<ClassObj>> result = |
| new TreeMap<String, Set<ClassObj>>(); |
| |
| Set<ClassObj> classes = new TreeSet<ClassObj>(); |
| |
| // Build a set of all classes across all heaps |
| for (Heap heap : snapshot.mHeaps) { |
| classes.addAll(heap.getClasses()); |
| } |
| |
| // Filter it if needed |
| if (excludedPrefixes != null) { |
| final int N = excludedPrefixes.length; |
| Iterator<ClassObj> iter = classes.iterator(); |
| |
| while (iter.hasNext()) { |
| ClassObj theClass = iter.next(); |
| String classPath = theClass.toString(); |
| |
| for (int i = 0; i < N; i++) { |
| if (classPath.startsWith(excludedPrefixes[i])) { |
| iter.remove(); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Now that we have a final list of classes, group them by package |
| for (ClassObj theClass : classes) { |
| String packageName = DEFAULT_PACKAGE; |
| int lastDot = theClass.mClassName.lastIndexOf('.'); |
| |
| if (lastDot != -1) { |
| packageName = theClass.mClassName.substring(0, lastDot); |
| } |
| |
| Set<ClassObj> classSet = result.get(packageName); |
| |
| if (classSet == null) { |
| classSet = new TreeSet<ClassObj>(); |
| result.put(packageName, classSet); |
| } |
| |
| classSet.add(theClass); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Returns a collection of classes common to both snapshots. |
| * |
| * <p>The query returns instances from the first snapshot. Note: two classes having the same |
| * fully-qualified name are considered equal, even if they differ in static fields, superclass, |
| * classloader, etc. |
| */ |
| @NonNull |
| public static Collection<ClassObj> commonClasses(@NonNull Snapshot first, |
| @NonNull Snapshot second) { |
| Collection<ClassObj> classes = new ArrayList<ClassObj>(); |
| for (Heap heap : first.getHeaps()) { |
| for (ClassObj clazz : heap.getClasses()) { |
| if (second.findClass(clazz.getClassName()) != null) { |
| classes.add(clazz); |
| } |
| } |
| } |
| return classes; |
| } |
| |
| /* |
| * It's sorta sad that this is a pass-through call, but it seems like |
| * having all of the hat-like query methods in one place is a good thing |
| * even if there is duplication of effort. |
| */ |
| public static ClassObj findClass(@NonNull Snapshot snapshot, String name) { |
| return snapshot.findClass(name); |
| } |
| |
| /* |
| * Return an array of instances of the given class. This does not include |
| * instances of subclasses. |
| */ |
| @NonNull |
| public static Instance[] instancesOf(@NonNull Snapshot snapshot, String baseClassName) { |
| ClassObj theClass = snapshot.findClass(baseClassName); |
| |
| if (theClass == null) { |
| throw new IllegalArgumentException("Class not found: " + baseClassName); |
| } |
| |
| List<Instance> instances = theClass.getInstancesList(); |
| return instances.toArray(new Instance[instances.size()]); |
| } |
| |
| /* |
| * Return an array of instances of the given class. This includes |
| * instances of subclasses. |
| */ |
| @NonNull |
| public static Instance[] allInstancesOf(@NonNull Snapshot snapshot, String baseClassName) { |
| ClassObj theClass = snapshot.findClass(baseClassName); |
| |
| if (theClass == null) { |
| throw new IllegalArgumentException("Class not found: " + baseClassName); |
| } |
| |
| ArrayList<ClassObj> classList = new ArrayList<ClassObj>(); |
| |
| classList.add(theClass); |
| classList.addAll(traverseSubclasses(theClass)); |
| |
| ArrayList<Instance> instanceList = new ArrayList<Instance>(); |
| |
| for (ClassObj someClass : classList) { |
| instanceList.addAll(someClass.getInstancesList()); |
| } |
| |
| Instance[] result = new Instance[instanceList.size()]; |
| |
| instanceList.toArray(result); |
| |
| return result; |
| } |
| |
| @NonNull |
| private static ArrayList<ClassObj> traverseSubclasses(@NonNull ClassObj base) { |
| ArrayList<ClassObj> result = new ArrayList<ClassObj>(); |
| |
| for (ClassObj subclass : base.mSubclasses) { |
| result.add(subclass); |
| result.addAll(traverseSubclasses(subclass)); |
| } |
| |
| return result; |
| } |
| |
| /* |
| * Find a reference to an object based on its id. The id should be |
| * in hexadecimal. |
| */ |
| public static Instance findObject(@NonNull Snapshot snapshot, String id) { |
| long id2 = Long.parseLong(id, 16); |
| |
| return snapshot.findInstance(id2); |
| } |
| |
| @NonNull |
| public static Collection<RootObj> getRoots(@NonNull Snapshot snapshot) { |
| HashSet<RootObj> result = new HashSet<RootObj>(); |
| |
| for (Heap heap : snapshot.mHeaps) { |
| result.addAll(heap.mRoots); |
| } |
| |
| return result; |
| } |
| |
| @NonNull |
| public static final Instance[] newInstances(@NonNull Snapshot older, @NonNull Snapshot newer) { |
| ArrayList<Instance> resultList = new ArrayList<Instance>(); |
| |
| for (Heap newHeap : newer.mHeaps) { |
| Heap oldHeap = older.getHeap(newHeap.getName()); |
| |
| if (oldHeap == null) { |
| continue; |
| } |
| |
| for (Instance instance : newHeap.getInstances()) { |
| Instance oldInstance = oldHeap.getInstance(instance.mId); |
| |
| /* |
| * If this instance wasn't in the old heap, or was there, |
| * but that ID was for an obj of a different type, then we have |
| * a newly allocated object and we should report it in the |
| * results. |
| */ |
| if (oldInstance == null || (instance.getClassObj() != oldInstance.getClassObj())) { |
| resultList.add(instance); |
| } |
| } |
| } |
| |
| Instance[] resultArray = new Instance[resultList.size()]; |
| |
| return resultList.toArray(resultArray); |
| } |
| } |