dk.ilios.spanner.internal.ExperimentingSpannerRun.java Source code

Java tutorial

Introduction

Here is the source code for dk.ilios.spanner.internal.ExperimentingSpannerRun.java

Source

/*
 * Copyright (C) 2011 Google Inc.
 * Copyright (C) 2015 Christian Melchior
 *
 * 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 dk.ilios.spanner.internal;

import com.google.common.base.Function;
import com.google.common.base.Stopwatch;
import com.google.common.base.Throwables;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureFallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.Uninterruptibles;

import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;

import dk.ilios.spanner.Spanner;
import dk.ilios.spanner.SpannerConfig;
import dk.ilios.spanner.output.ResultProcessor;
import dk.ilios.spanner.exception.SkipThisScenarioException;
import dk.ilios.spanner.exception.TrialFailureException;
import dk.ilios.spanner.benchmark.BenchmarkClass;
import dk.ilios.spanner.trial.AndroidTrial;
import dk.ilios.spanner.trial.ScheduledTrial;
import dk.ilios.spanner.trial.TrialContext;
import dk.ilios.spanner.model.BenchmarkSpec;
import dk.ilios.spanner.model.Host;
import dk.ilios.spanner.model.InstrumentSpec;
import dk.ilios.spanner.model.Run;
import dk.ilios.spanner.model.Scenario;
import dk.ilios.spanner.model.Trial;
import dk.ilios.spanner.trial.TrialSchedulingPolicy;
import dk.ilios.spanner.log.StdOut;

/**
 * An execution of each {@link Experiment} for the configured number of trials.
 */
public final class ExperimentingSpannerRun implements SpannerRun {

    private static final Logger logger = Logger.getLogger(ExperimentingSpannerRun.class.getName());

    private static final FutureFallback<Object> FALLBACK_TO_NULL = new FutureFallback<Object>() {
        final ListenableFuture<Object> nullFuture = Futures.immediateFuture(null);

        @Override
        public ListenableFuture<Object> create(Throwable t) throws Exception {
            return nullFuture;
        }
    };

    private final SpannerConfig options;
    private final StdOut stdout;
    private final Run runInfo;
    private final ImmutableSet<ResultProcessor> resultProcessors;
    private final ExperimentSelector selector;
    private final ListeningExecutorService executorProvider;
    private final Spanner.Callback callback;
    private final Trial[] baselineData;

    public ExperimentingSpannerRun(SpannerConfig configuration, StdOut stdout, Run runInfo,
            ImmutableSet<ResultProcessor> resultProcessors, ExperimentSelector selector,
            ListeningExecutorService executorProvider, Trial[] baselineData, Spanner.Callback callback) {
        this.options = configuration;
        this.stdout = stdout;
        this.runInfo = runInfo;
        this.resultProcessors = resultProcessors;
        this.selector = selector;
        this.executorProvider = executorProvider;
        this.baselineData = baselineData;
        this.callback = callback;
    }

    @Override
    public void run() throws InvalidBenchmarkException {

        ImmutableSet<Experiment> allExperiments = selector.selectExperiments(baselineData);

        // TODO(lukes): move this standard-out handling into the ConsoleOutput class?
        stdout.println("Experiment selection: ");
        stdout.println("  Benchmark Methods:   "
                + FluentIterable.from(allExperiments).transform(new Function<Experiment, String>() {
                    @Override
                    public String apply(Experiment experiment) {
                        return experiment.instrumentation().benchmarkMethod().getName();
                    }
                }).toSet());
        stdout.println("  Instruments:   "
                + FluentIterable.from(selector.instruments()).transform(new Function<Instrument, String>() {
                    @Override
                    public String apply(Instrument instrument) {
                        return instrument.name();
                    }
                }));
        stdout.println("  User parameters:   " + selector.userParameters());
        stdout.println("  Selection type:    " + selector.selectionType());
        stdout.println();

        stdout.format("This selection yields %s experiments.%n", allExperiments.size());
        stdout.flush();

        // always dry run first.
        ImmutableSet<Experiment> experimentsToRun = dryRun(allExperiments);
        //        if (experimentsToRun.size() != allExperiments.size()) {
        //            stdout.format("%d experiments were skipped.%n", allExperiments.size() - experimentsToRun.size());
        //        }

        //        if (experimentsToRun.isEmpty()) {
        //            throw new InvalidBenchmarkException("All experiments were skipped.");
        //        }
        //
        //        if (options.dryRun()) {
        //            return;
        //        }

        stdout.flush();

        int totalTrials = experimentsToRun.size() * options.getTrialsPrExperiment();
        Stopwatch stopwatch = Stopwatch.createStarted();
        List<ScheduledTrial> trials = createScheduledTrials(experimentsToRun, totalTrials);

        List<ListenableFuture<Trial.Result>> pendingTrials = scheduleTrials(trials, executorProvider);
        ConsoleOutput output = new ConsoleOutput(stdout, totalTrials, stopwatch);
        try {
            // Process results as they complete.
            for (ListenableFuture<Trial.Result> trialFuture : inCompletionOrder(pendingTrials)) {
                try {
                    Trial.Result result = trialFuture.get();
                    output.processTrial(result);
                    for (ResultProcessor resultProcessor : resultProcessors) {
                        resultProcessor.processTrial(result.getTrial());
                    }
                } catch (ExecutionException e) {
                    if (e.getCause() instanceof TrialFailureException) {
                        output.processFailedTrial((TrialFailureException) e.getCause());
                    } else {
                        for (ListenableFuture<?> toCancel : pendingTrials) {
                            toCancel.cancel(true);
                        }
                        throw Throwables.propagate(e.getCause());
                    }
                } catch (InterruptedException e) {
                    // be responsive to interruption, cancel outstanding work and exit
                    for (ListenableFuture<?> toCancel : pendingTrials) {
                        // N.B. TrialRunLoop is responsive to interruption.
                        toCancel.cancel(true);
                    }
                    throw new RuntimeException(e);
                }
            }
        } finally {
            executorProvider.shutdown();
            output.close();
        }

        for (ResultProcessor resultProcessor : resultProcessors) {
            try {
                resultProcessor.close();
            } catch (IOException e) {
                logger.log(Level.WARNING, "Could not close a result processor: " + resultProcessor, e);
            }
        }
    }

    /**
     * Schedule all the trials.
     * <p>
     * <p>This method arranges all the {@link ScheduledTrial trials} to run according to their
     * scheduling criteria.  The executor instance is responsible for enforcing max parallelism.
     */
    private List<ListenableFuture<Trial.Result>> scheduleTrials(List<ScheduledTrial> trials,
            final ListeningExecutorService executor) {
        List<ListenableFuture<Trial.Result>> pendingTrials = Lists.newArrayList();
        List<ScheduledTrial> serialTrials = Lists.newArrayList();
        for (final ScheduledTrial scheduledTrial : trials) {
            if (scheduledTrial.policy() == TrialSchedulingPolicy.PARALLEL) {
                pendingTrials.add(executor.submit(scheduledTrial.trialTask()));
            } else {
                serialTrials.add(scheduledTrial);
            }
        }
        // A future representing the completion of all prior tasks. Futures.successfulAsList allows us
        // to ignore failure.
        ListenableFuture<?> previous = Futures.successfulAsList(pendingTrials);
        for (final ScheduledTrial scheduledTrial : serialTrials) {
            // each of these trials can only start after all prior trials have finished, so we use
            // Futures.transform to force the sequencing.
            ListenableFuture<Trial.Result> current = Futures.transform(previous,
                    new AsyncFunction<Object, Trial.Result>() {
                        @Override
                        public ListenableFuture<Trial.Result> apply(Object ignored) {
                            return executor.submit(scheduledTrial.trialTask());
                        }
                    });
            pendingTrials.add(current);
            // ignore failure of the prior task.
            previous = Futures.withFallback(current, FALLBACK_TO_NULL);
        }
        return pendingTrials;
    }

    /**
     * Returns all the ScheduledTrials for this run.
     */
    private List<ScheduledTrial> createScheduledTrials(ImmutableSet<Experiment> experimentsToRun, int totalTrials) {
        List<ScheduledTrial> trials = Lists.newArrayListWithCapacity(totalTrials);
        for (Experiment experiment : experimentsToRun) {
            for (int i = 0; i < options.getTrialsPrExperiment(); i++) {

                BenchmarkSpec benchmarkSpec = experiment.benchmarkSpec();

                MeasurementCollectingVisitor measurementsVisitor = experiment.instrumentation()
                        .getMeasurementCollectingVisitor();

                InstrumentSpec instrumentSpec = experiment.instrumentation().instrument().getSpec();

                Scenario scenario = new Scenario.Builder().benchmarkSpec(benchmarkSpec)
                        .host(new Host.Builder().build()).build();

                // Create a trial from the unique combination of trial number,
                /** This is 1-indexed because it's only used for display to users.  E.g. "Trial 1 of 27" */
                int trialNumber = i + 1;
                TrialContext trialContext = new TrialContext(UUID.randomUUID(), trialNumber, experiment);
                Trial trial = new Trial.Builder(trialContext).run(runInfo).scenario(scenario)
                        .instrumentSpec(instrumentSpec).build();

                AndroidTrial runLoop = new AndroidTrial(trial, selector.benchmarkClass(), measurementsVisitor,
                        callback);
                ScheduledTrial scheduledTrial = new ScheduledTrial(trial, runLoop, TrialSchedulingPolicy.SERIAL);
                trials.add(scheduledTrial);
            }
        }
        return trials;
    }

    /**
     * Attempts to run each given scenario once, in the current VM. Returns a set of all of the
     * scenarios that didn't throw a {@link SkipThisScenarioException}.
     */
    ImmutableSet<Experiment> dryRun(Iterable<Experiment> experiments) throws InvalidBenchmarkException {
        ImmutableSet.Builder<Experiment> builder = ImmutableSet.builder();
        for (Experiment experiment : experiments) {
            try {
                BenchmarkClass benchmarkClass = selector.benchmarkClass();
                Object benchmarkInstance = selector.benchmarkClass().getInstance();
                //                benchmarkClass.setUpBenchmark(benchmarkInstance);
                try {
                    //                    experiment.instrumentation().dryRun(benchmarkInstance);
                    builder.add(experiment);
                } finally {
                    // discard 'benchmark' now; the worker will have to instantiate its own anyway
                    //                    benchmarkClass.cleanup(benchmarkInstance);
                }
            } catch (SkipThisScenarioException ignored) {
            }
        }
        return builder.build();
    }

    public static <T> ImmutableList<ListenableFuture<T>> inCompletionOrder(
            Iterable<? extends ListenableFuture<? extends T>> futures) {
        final ConcurrentLinkedQueue<SettableFuture<T>> delegates = Queues.newConcurrentLinkedQueue();
        ImmutableList.Builder<ListenableFuture<T>> listBuilder = ImmutableList.builder();
        Executor executor = MoreExecutors.sameThreadExecutor();
        for (final ListenableFuture<? extends T> future : futures) {
            SettableFuture<T> delegate = SettableFuture.create();
            // Must make sure to add the delegate to the queue first in case the future is already done
            delegates.add(delegate);
            future.addListener(new Runnable() {
                @Override
                public void run() {
                    SettableFuture<T> delegate = delegates.remove();
                    try {
                        delegate.set(Uninterruptibles.getUninterruptibly(future));
                    } catch (ExecutionException e) {
                        delegate.setException(e.getCause());
                    } catch (CancellationException e) {
                        delegate.cancel(true);
                    }
                }
            }, executor);
            listBuilder.add(delegate);
        }
        return listBuilder.build();
    }
}