• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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