blob: af3634c8ce431d3f6e6d03e4419c0ff35c191cd0 [file] [log] [blame]
/*
* Copyright (C) 2013 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.worker;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.caliper.model.Measurement;
import com.google.caliper.model.Value;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Multisets;
import java.text.DecimalFormat;
import java.util.Collection;
/**
* A set of statistics about the allocations performed by a benchmark method.
*/
final class AllocationStats {
private final int allocationCount;
private final long allocationSize;
private final int reps;
private final ImmutableMultiset<Allocation> allocations;
/**
* Constructs a new {@link AllocationStats} with the given number of allocations
* ({@code allocationCount}), cumulative size of the allocations ({@code allocationSize}) and the
* number of {@code reps} passed to the benchmark method.
*/
AllocationStats(int allocationCount, long allocationSize, int reps) {
this(allocationCount, allocationSize, reps, ImmutableMultiset.<Allocation>of());
}
/**
* Constructs a new {@link AllocationStats} with the given allocations and the number of
* {@code reps} passed to the benchmark method.
*/
AllocationStats(Collection<Allocation> allocations, int reps) {
this(allocations.size(), Allocation.getTotalSize(allocations), reps,
ImmutableMultiset.copyOf(allocations));
}
private AllocationStats(int allocationCount, long allocationSize, int reps,
Multiset<Allocation> allocations) {
checkArgument(allocationCount >= 0, "allocationCount (%s) was negative", allocationCount);
this.allocationCount = allocationCount;
checkArgument(allocationSize >= 0, "allocationSize (%s) was negative", allocationSize);
this.allocationSize = allocationSize;
checkArgument(reps >= 0, "reps (%s) was negative", reps);
this.reps = reps;
this.allocations = Multisets.copyHighestCountFirst(allocations);
}
int getAllocationCount() {
return allocationCount;
}
long getAllocationSize() {
return allocationSize;
}
/**
* Computes and returns the difference between this measurement and the given
* {@code baseline} measurement. The {@code baseline} measurement must have a lower weight
* (fewer reps) than this measurement.
*/
AllocationStats minus(AllocationStats baseline) {
for (Entry<Allocation> entry : baseline.allocations.entrySet()) {
int superCount = allocations.count(entry.getElement());
if (superCount < entry.getCount()) {
throw new IllegalStateException(
String.format("Your benchmark appears to have non-deterministic allocation behavior. "
+ "Observed %d instance(s) of %s in the baseline but only %d in the actual "
+ "measurement",
entry.getCount(),
entry.getElement(),
superCount));
}
}
try {
return new AllocationStats(allocationCount - baseline.allocationCount,
allocationSize - baseline.allocationSize,
reps - baseline.reps,
Multisets.difference(allocations, baseline.allocations));
} catch (IllegalArgumentException e) {
throw new IllegalStateException(String.format(
"Your benchmark appears to have non-deterministic allocation behavior. The difference "
+ "between the baseline %s and the measurement %s is invalid. Consider enabling "
+ "instrument.allocation.options.trackAllocations to get a more specific error message.",
baseline, this), e);
}
}
/**
* Computes and returns the difference between this measurement and the given
* {@code baseline} measurement. Unlike {@link #minus(AllocationStats)} this does not have to
* be a super set of the baseline.
*/
public Delta delta(AllocationStats baseline) {
return new Delta(
allocationCount - baseline.allocationCount,
allocationSize - baseline.allocationSize,
reps - baseline.reps,
Multisets.difference(allocations, baseline.allocations),
Multisets.difference(baseline.allocations, allocations));
}
/**
* Returns a list of {@link Measurement measurements} based on this collection of stats.
*/
ImmutableList<Measurement> toMeasurements() {
for (Entry<Allocation> entry : allocations.entrySet()) {
double allocsPerRep = ((double) entry.getCount()) / reps;
System.out.printf("Allocated %f allocs per rep of %s%n", allocsPerRep, entry.getElement());
}
return ImmutableList.of(
new Measurement.Builder()
.value(Value.create(allocationCount, ""))
.description("objects")
.weight(reps)
.build(),
new Measurement.Builder()
.value(Value.create(allocationSize, "B"))
.weight(reps)
.description("bytes")
.build());
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof AllocationStats) {
AllocationStats that = (AllocationStats) obj;
return allocationCount == that.allocationCount
&& allocationSize == that.allocationSize
&& reps == that.reps
&& Objects.equal(allocations, that.allocations);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hashCode(allocationCount, allocationSize, reps, allocations);
}
@Override public String toString() {
return MoreObjects.toStringHelper(this)
.add("allocationCount", allocationCount)
.add("allocationSize", allocationSize)
.add("reps", reps)
.add("allocations", allocations)
.toString();
}
/**
* The delta between two different sets of statistics.
*/
static final class Delta {
private final int count;
private final long size;
private final int reps;
private final Multiset<Allocation> additions;
private final Multiset<Allocation> removals;
Delta(
int count,
long size,
int reps,
Multiset<Allocation> additions,
Multiset<Allocation> removals) {
this.count = count;
this.size = size;
this.reps = reps;
this.additions = additions;
this.removals = removals;
}
/**
* Returns the long formatted with a leading +/- sign
*/
private static String formatWithLeadingSign(long n) {
return n > 0 ? "+" + n : "" + n;
}
@Override public String toString() {
return MoreObjects.toStringHelper(this)
.add("count", formatWithLeadingSign(count))
.add("size", formatWithLeadingSign(size))
.add("reps", formatWithLeadingSign(reps))
.add("additions", additions)
.add("removals", removals)
.toString();
}
}
}