blob: 3cacaeba59703dadb7a63f9ebd154efe96e8f2ab [file] [log] [blame]
/*
* Copyright (C) 2012 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.google.caliper.memory;
import com.google.caliper.memory.ObjectExplorer.Feature;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multiset;
import java.util.EnumSet;
/**
* A tool that can qualitatively measure the footprint
* ({@literal e.g.}, number of objects, references,
* primitives) of a graph structure.
*/
public final class ObjectGraphMeasurer {
/**
* The footprint of an object graph.
*/
public final static class Footprint {
private final int objects;
private final int nonNullRefs;
private final int nullRefs;
private final ImmutableMultiset<Class<?>> primitives;
private static final ImmutableSet<Class<?>> primitiveTypes = ImmutableSet.<Class<?>>of(
boolean.class, byte.class, char.class, short.class,
int.class, float.class, long.class, double.class);
/**
* Constructs a Footprint, by specifying the number of objects,
* references, and primitives (represented as a {@link Multiset}).
*
* @param objects the number of objects
* @param nonNullRefs the number of non-null references
* @param nullRefs the number of null references
* @param primitives the number of primitives (represented by the
* respective primitive classes, e.g. {@code int.class} etc)
*/
public Footprint(int objects, int nonNullRefs, int nullRefs,
Multiset<Class<?>> primitives) {
Preconditions.checkArgument(objects >= 0, "Negative number of objects");
Preconditions.checkArgument(nonNullRefs >= 0, "Negative number of references");
Preconditions.checkArgument(nullRefs >= 0, "Negative number of references");
Preconditions.checkArgument(primitiveTypes.containsAll(primitives.elementSet()),
"Unexpected primitive type");
this.objects = objects;
this.nonNullRefs = nonNullRefs;
this.nullRefs = nullRefs;
this.primitives = ImmutableMultiset.copyOf(primitives);
}
/**
* Returns the number of objects of this footprint.
*/
public int getObjects() {
return objects;
}
/**
* Returns the number of non-null references of this footprint.
*/
public int getNonNullReferences() {
return nonNullRefs;
}
/**
* Returns the number of null references of this footprint.
*/
public int getNullReferences() {
return nullRefs;
}
/**
* Returns the number of all references (null and non-null) of this footprint.
*/
public int getAllReferences() {
return nonNullRefs + nullRefs;
}
/**
* Returns the number of primitives of this footprint
* (represented by the respective primitive classes,
* {@literal e.g.} {@code int.class} etc).
*/
public ImmutableMultiset<Class<?>> getPrimitives() {
return primitives;
}
@Override
public int hashCode() {
return Objects.hashCode(getClass().getName(),
objects, nonNullRefs, nullRefs, primitives);
}
@Override
public boolean equals(Object o) {
if (o instanceof Footprint) {
Footprint that = (Footprint) o;
return this.objects == that.objects
&& this.nonNullRefs == that.nonNullRefs
&& this.nullRefs == that.nullRefs
&& this.primitives.equals(that.primitives);
}
return false;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("Objects", objects)
.add("NonNullRefs", nonNullRefs)
.add("NullRefs", nullRefs)
.add("Primitives", primitives)
.toString();
}
}
/**
* Measures the footprint of the specified object graph.
* The object graph is defined by a root object and whatever object can be
* reached through that, excluding static fields, {@code Class} objects,
* and fields defined in {@code enum}s (all these are considered shared
* values, which should not contribute to the cost of any single object
* graph).
*
* <p>Equivalent to {@code measure(rootObject, Predicates.alwaysTrue())}.
*
* @param rootObject the root object of the object graph
* @return the footprint of the object graph
*/
public static Footprint measure(Object rootObject) {
return measure(rootObject, Predicates.alwaysTrue());
}
/**
* Measures the footprint of the specified object graph.
* The object graph is defined by a root object and whatever object can be
* reached through that, excluding static fields, {@code Class} objects,
* and fields defined in {@code enum}s (all these are considered shared
* values, which should not contribute to the cost of any single object
* graph), and any object for which the user-provided predicate returns
* {@code false}.
*
* @param rootObject the root object of the object graph
* @param objectAcceptor a predicate that returns {@code true} for objects
* to be explored (and treated as part of the footprint), or {@code false}
* to forbid the traversal to traverse the given object
* @return the footprint of the object graph
*/
public static Footprint measure(Object rootObject, Predicate<Object> objectAcceptor) {
Preconditions.checkNotNull(objectAcceptor, "predicate");
Predicate<Chain> completePredicate = Predicates.and(ImmutableList.of(
ObjectExplorer.notEnumFieldsOrClasses,
new ObjectExplorer.AtMostOncePredicate(),
Predicates.compose(objectAcceptor, ObjectExplorer.chainToObject)
));
return ObjectExplorer.exploreObject(rootObject, new ObjectGraphVisitor(completePredicate),
EnumSet.of(Feature.VISIT_PRIMITIVES, Feature.VISIT_NULL));
}
private static class ObjectGraphVisitor implements ObjectVisitor<Footprint> {
private int objects;
// -1 to account for the root, which has no reference leading to it
private int nonNullReferences = -1;
private int nullReferences = 0;
private final Multiset<Class<?>> primitives = HashMultiset.create();
private final Predicate<Chain> predicate;
ObjectGraphVisitor(Predicate<Chain> predicate) {
this.predicate = predicate;
}
@Override public Traversal visit(Chain chain) {
if (chain.isPrimitive()) {
primitives.add(chain.getValueType());
return Traversal.SKIP;
} else {
if (chain.getValue() == null) {
nullReferences++;
} else {
nonNullReferences++;
}
}
if (predicate.apply(chain) && chain.getValue() != null) {
objects++;
return Traversal.EXPLORE;
}
return Traversal.SKIP;
}
@Override public Footprint result() {
return new Footprint(objects, nonNullReferences, nullReferences,
ImmutableMultiset.copyOf(primitives));
}
}
private ObjectGraphMeasurer() {}
}