Java tutorial
/* * 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. * * Original author gak@google.com (Gregory Kick) */ package dk.ilios.spanner.model; import com.google.common.base.Defaults; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; import org.apache.commons.math.stat.descriptive.rank.Percentile; import java.util.ArrayList; import java.util.List; import java.util.UUID; import dk.ilios.spanner.internal.Experiment; import dk.ilios.spanner.json.ExcludeFromJson; import dk.ilios.spanner.trial.TrialContext; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; /** * An invocation of a single {@link Experiment}. * <p> * Once a trial is completed, call {@link #getResult()} to get the result. From that point, all further modifications * to the object Trial object will fail. */ public final class Trial { private UUID id; private Run run; private InstrumentSpec instrumentSpec; private Scenario scenario; private List<Measurement> measurements = new ArrayList<>(); private List<String> messages = new ArrayList<>(); private Experiment experiment; @ExcludeFromJson private int trialNumber; @ExcludeFromJson private boolean trialComplete; @ExcludeFromJson private boolean resultsCalculated; @ExcludeFromJson private Percentile percentile; @ExcludeFromJson private DescriptiveStatistics descriptiveStatistics; private Trial(Builder builder) { this.trialNumber = builder.trialNumber; this.id = builder.id; this.run = builder.run; this.instrumentSpec = builder.instrumentSpec; this.scenario = builder.scenario; this.experiment = builder.experiment; } public UUID id() { return id; } public Run run() { return run; } public InstrumentSpec instrumentSpec() { return instrumentSpec; } public Scenario scenario() { return scenario; } public Experiment experiment() { return experiment; } public List<Measurement> measurements() { return measurements; } public void addMeasurement(Measurement.Builder measurementBuilder) { checkIsComplete(); addMeasurement(measurementBuilder.build()); } public void addMeasurement(Measurement measurement) { checkIsComplete(); this.measurements.add(measurement); } public void addAllMeasurements(Iterable<Measurement> measurements) { checkIsComplete(); Iterables.addAll(this.measurements, measurements); } public void addAllMessages(Iterable<String> messages) { checkIsComplete(); Iterables.addAll(this.messages, messages); } public void addMessage(String message) { checkIsComplete(); messages.add(message); } public int getTrialNumber() { return trialNumber; } /** * Mark Trial as done and calculate results. */ public void calculateResults() { checkResultsCalculated(false); double[] weightedValues = new double[measurements.size()]; int i = 0; for (Measurement measurement : measurements) { weightedValues[i] = measurement.value().magnitude() / measurement.weight(); i++; } percentile = new Percentile(); percentile.setData(weightedValues); descriptiveStatistics = new DescriptiveStatistics(weightedValues); if (experiment.getBaseline() != null) { experiment.getBaseline().calculateResults(); } resultsCalculated = true; } private void checkResultsCalculated(boolean calculated) { if (calculated && !resultsCalculated) { throw new IllegalStateException("Results have not been calculated."); } else if (!calculated && resultsCalculated) { throw new IllegalStateException("Results have already been calculated."); } } /** * Returns the results. Will calculate results if not already done */ public Result getResult() { trialComplete = true; calculateResults(); return new Result(this, experiment, ImmutableList.copyOf(messages)); } public double getMin() { checkResultsCalculated(true); return descriptiveStatistics.getMin(); } public double getMax() { checkResultsCalculated(true); return descriptiveStatistics.getMax(); } public double getMean() { checkResultsCalculated(true); return descriptiveStatistics.getMean(); } /** * Return the benchmark value at the given percentile. * * @param percentile [0.0, 100.0] */ public double getPercentile(float percentile) { checkResultsCalculated(true); if (percentile == 0.0f) { return descriptiveStatistics.getMin(); } else if (percentile == 100.0f) { return descriptiveStatistics.getMax(); } else { return this.percentile.evaluate(percentile); } } public double getMedian() { checkResultsCalculated(true); return this.percentile.evaluate(50); } public boolean hasBaseline() { return experiment.getBaseline() != null; } /** * Returns changes from baseline at the given percentile. * * @param percentile [0.0F, 100.0F] * @return Change in percent from baseline. {@code 1.0} is 100%. */ public double getChangeFromBaseline(float percentile) { checkResultsCalculated(true); if (experiment.getBaseline() == null) { throw new IllegalStateException("No baseline exists"); } double newMedian = getPercentile(percentile); double oldMedian = experiment.getBaseline().getMedian(); return (oldMedian - newMedian) / oldMedian; } public Double getChangeFromBaselineMean() { checkResultsCalculated(true); if (experiment.getBaseline() == null) return null; double newMean = getMean(); double oldMean = experiment.getBaseline().getMean(); return (oldMean - newMean) / oldMean; } public String getUnit() { if (measurements.size() == 0) { return ""; } else { return measurements.get(0).value().unit(); } } private void checkIsComplete() { if (trialComplete) { throw new RuntimeException("Trial is complete. No further modifications are allowed"); } } @Override public boolean equals(Object obj) { if (obj == this) { return true; } else if (obj instanceof Trial) { Trial that = (Trial) obj; return this.id.equals(that.id) && this.run.equals(that.run) && this.instrumentSpec.equals(that.instrumentSpec) && this.scenario.equals(that.scenario) && this.measurements.equals(that.measurements) && this.experiment.equals(that.experiment) && this.messages.equals(that.messages) && this.trialComplete == that.trialComplete; } else { return false; } } @Override public int hashCode() { return Objects.hashCode(id, run, instrumentSpec, scenario, experiment, measurements, messages, trialComplete); } @Override public String toString() { return Objects.toStringHelper(this).add("id", id).add("run", run).add("instrumentSpec", instrumentSpec) .add("scenario", scenario).add("experiment", experiment).add("measurements", measurements) .add("messages", messages).add("trialComplete", trialComplete).toString(); } /** * Builder for making Trial objects that are ready to be run. */ public static final class Builder { private final UUID id; private final int trialNumber; private Run run; private InstrumentSpec instrumentSpec; private Scenario scenario; private Experiment experiment; public Builder(TrialContext context) { checkNotNull(context); this.id = context.getTrialId(); this.experiment = context.getExperiment(); this.trialNumber = context.getTrialNumber(); } public Builder run(Run.Builder runBuilder) { return run(runBuilder.build()); } public Builder run(Run run) { this.run = checkNotNull(run); return this; } public Builder instrumentSpec(InstrumentSpec instrumentSpec) { this.instrumentSpec = checkNotNull(instrumentSpec); return this; } public Builder scenario(Scenario scenario) { this.scenario = checkNotNull(scenario); return this; } public Trial build() { checkState(run != null); checkState(instrumentSpec != null); checkState(scenario != null); checkState(experiment != null); return new Trial(this); } } /** * Container for the results of the current trail. */ public class Result { private final Trial trial; private final Experiment experiment; private final ImmutableList<String> trialMessages; Result(Trial trial, Experiment experiment, ImmutableList<String> trialMessages) { this.trial = trial; this.experiment = experiment; this.trialMessages = trialMessages; } public Experiment getExperiment() { return experiment; } public Trial getTrial() { return trial; } public ImmutableList<String> getTrialMessages() { return trialMessages; } } }