1 /* 2 * Copyright (C) 2012 Google Inc. 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.google.caliper.memory; 18 19 import com.google.caliper.memory.ObjectExplorer.Feature; 20 import com.google.common.base.MoreObjects; 21 import com.google.common.base.Objects; 22 import com.google.common.base.Preconditions; 23 import com.google.common.base.Predicate; 24 import com.google.common.base.Predicates; 25 import com.google.common.collect.HashMultiset; 26 import com.google.common.collect.ImmutableList; 27 import com.google.common.collect.ImmutableMultiset; 28 import com.google.common.collect.ImmutableSet; 29 import com.google.common.collect.Multiset; 30 31 import java.util.EnumSet; 32 33 /** 34 * A tool that can qualitatively measure the footprint 35 * ({@literal e.g.}, number of objects, references, 36 * primitives) of a graph structure. 37 */ 38 public final class ObjectGraphMeasurer { 39 /** 40 * The footprint of an object graph. 41 */ 42 public final static class Footprint { 43 private final int objects; 44 private final int nonNullRefs; 45 private final int nullRefs; 46 private final ImmutableMultiset<Class<?>> primitives; 47 48 private static final ImmutableSet<Class<?>> primitiveTypes = ImmutableSet.<Class<?>>of( 49 boolean.class, byte.class, char.class, short.class, 50 int.class, float.class, long.class, double.class); 51 52 /** 53 * Constructs a Footprint, by specifying the number of objects, 54 * references, and primitives (represented as a {@link Multiset}). 55 * 56 * @param objects the number of objects 57 * @param nonNullRefs the number of non-null references 58 * @param nullRefs the number of null references 59 * @param primitives the number of primitives (represented by the 60 * respective primitive classes, e.g. {@code int.class} etc) 61 */ Footprint(int objects, int nonNullRefs, int nullRefs, Multiset<Class<?>> primitives)62 public Footprint(int objects, int nonNullRefs, int nullRefs, 63 Multiset<Class<?>> primitives) { 64 Preconditions.checkArgument(objects >= 0, "Negative number of objects"); 65 Preconditions.checkArgument(nonNullRefs >= 0, "Negative number of references"); 66 Preconditions.checkArgument(nullRefs >= 0, "Negative number of references"); 67 Preconditions.checkArgument(primitiveTypes.containsAll(primitives.elementSet()), 68 "Unexpected primitive type"); 69 this.objects = objects; 70 this.nonNullRefs = nonNullRefs; 71 this.nullRefs = nullRefs; 72 this.primitives = ImmutableMultiset.copyOf(primitives); 73 } 74 75 /** 76 * Returns the number of objects of this footprint. 77 */ getObjects()78 public int getObjects() { 79 return objects; 80 } 81 82 /** 83 * Returns the number of non-null references of this footprint. 84 */ getNonNullReferences()85 public int getNonNullReferences() { 86 return nonNullRefs; 87 } 88 89 /** 90 * Returns the number of null references of this footprint. 91 */ getNullReferences()92 public int getNullReferences() { 93 return nullRefs; 94 } 95 96 /** 97 * Returns the number of all references (null and non-null) of this footprint. 98 */ getAllReferences()99 public int getAllReferences() { 100 return nonNullRefs + nullRefs; 101 } 102 103 /** 104 * Returns the number of primitives of this footprint 105 * (represented by the respective primitive classes, 106 * {@literal e.g.} {@code int.class} etc). 107 */ getPrimitives()108 public ImmutableMultiset<Class<?>> getPrimitives() { 109 return primitives; 110 } 111 112 @Override hashCode()113 public int hashCode() { 114 return Objects.hashCode(getClass().getName(), 115 objects, nonNullRefs, nullRefs, primitives); 116 } 117 118 @Override equals(Object o)119 public boolean equals(Object o) { 120 if (o instanceof Footprint) { 121 Footprint that = (Footprint) o; 122 return this.objects == that.objects 123 && this.nonNullRefs == that.nonNullRefs 124 && this.nullRefs == that.nullRefs 125 && this.primitives.equals(that.primitives); 126 } 127 return false; 128 } 129 130 @Override toString()131 public String toString() { 132 return MoreObjects.toStringHelper(this) 133 .add("Objects", objects) 134 .add("NonNullRefs", nonNullRefs) 135 .add("NullRefs", nullRefs) 136 .add("Primitives", primitives) 137 .toString(); 138 } 139 } 140 141 /** 142 * Measures the footprint of the specified object graph. 143 * The object graph is defined by a root object and whatever object can be 144 * reached through that, excluding static fields, {@code Class} objects, 145 * and fields defined in {@code enum}s (all these are considered shared 146 * values, which should not contribute to the cost of any single object 147 * graph). 148 * 149 * <p>Equivalent to {@code measure(rootObject, Predicates.alwaysTrue())}. 150 * 151 * @param rootObject the root object of the object graph 152 * @return the footprint of the object graph 153 */ measure(Object rootObject)154 public static Footprint measure(Object rootObject) { 155 return measure(rootObject, Predicates.alwaysTrue()); 156 } 157 158 /** 159 * Measures the footprint of the specified object graph. 160 * The object graph is defined by a root object and whatever object can be 161 * reached through that, excluding static fields, {@code Class} objects, 162 * and fields defined in {@code enum}s (all these are considered shared 163 * values, which should not contribute to the cost of any single object 164 * graph), and any object for which the user-provided predicate returns 165 * {@code false}. 166 * 167 * @param rootObject the root object of the object graph 168 * @param objectAcceptor a predicate that returns {@code true} for objects 169 * to be explored (and treated as part of the footprint), or {@code false} 170 * to forbid the traversal to traverse the given object 171 * @return the footprint of the object graph 172 */ measure(Object rootObject, Predicate<Object> objectAcceptor)173 public static Footprint measure(Object rootObject, Predicate<Object> objectAcceptor) { 174 Preconditions.checkNotNull(objectAcceptor, "predicate"); 175 176 Predicate<Chain> completePredicate = Predicates.and(ImmutableList.of( 177 ObjectExplorer.notEnumFieldsOrClasses, 178 new ObjectExplorer.AtMostOncePredicate(), 179 Predicates.compose(objectAcceptor, ObjectExplorer.chainToObject) 180 )); 181 182 return ObjectExplorer.exploreObject(rootObject, new ObjectGraphVisitor(completePredicate), 183 EnumSet.of(Feature.VISIT_PRIMITIVES, Feature.VISIT_NULL)); 184 } 185 186 private static class ObjectGraphVisitor implements ObjectVisitor<Footprint> { 187 private int objects; 188 // -1 to account for the root, which has no reference leading to it 189 private int nonNullReferences = -1; 190 private int nullReferences = 0; 191 private final Multiset<Class<?>> primitives = HashMultiset.create(); 192 private final Predicate<Chain> predicate; 193 ObjectGraphVisitor(Predicate<Chain> predicate)194 ObjectGraphVisitor(Predicate<Chain> predicate) { 195 this.predicate = predicate; 196 } 197 visit(Chain chain)198 @Override public Traversal visit(Chain chain) { 199 if (chain.isPrimitive()) { 200 primitives.add(chain.getValueType()); 201 return Traversal.SKIP; 202 } else { 203 if (chain.getValue() == null) { 204 nullReferences++; 205 } else { 206 nonNullReferences++; 207 } 208 } 209 if (predicate.apply(chain) && chain.getValue() != null) { 210 objects++; 211 return Traversal.EXPLORE; 212 } 213 return Traversal.SKIP; 214 } 215 result()216 @Override public Footprint result() { 217 return new Footprint(objects, nonNullReferences, nullReferences, 218 ImmutableMultiset.copyOf(primitives)); 219 } 220 } 221 ObjectGraphMeasurer()222 private ObjectGraphMeasurer() {} 223 } 224