/* Copyright 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 org.arbeitspferde.groningen;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import org.arbeitspferde.groningen.Datastore.DatastoreException;
import org.arbeitspferde.groningen.HistoryDatastore.HistoryDatastoreException;
import org.arbeitspferde.groningen.common.BlockScope;
import org.arbeitspferde.groningen.common.EvaluatedSubject;
import org.arbeitspferde.groningen.config.ConfigManager;
import org.arbeitspferde.groningen.config.GroningenConfig;
import org.arbeitspferde.groningen.config.NamedConfigParam;
import org.arbeitspferde.groningen.config.PipelineIterationScoped;
import org.arbeitspferde.groningen.config.PipelineScoped;
import org.arbeitspferde.groningen.display.DisplayMediator;
import org.arbeitspferde.groningen.display.Displayable;
import org.arbeitspferde.groningen.display.MonitorGroningen;
import org.arbeitspferde.groningen.experimentdb.ExperimentDb;
import org.arbeitspferde.groningen.experimentdb.FitnessScore;
import org.arbeitspferde.groningen.experimentdb.SubjectStateBridge;
import org.arbeitspferde.groningen.generator.SubjectShuffler;
import org.arbeitspferde.groningen.hypothesizer.Hypothesizer;
import org.arbeitspferde.groningen.proto.Params.GroningenParams;
import org.arbeitspferde.groningen.utility.Clock;
import org.arbeitspferde.groningen.utility.MetricExporter;
import org.joda.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class encapsulates a Groningen pipeline.
*
* TODO(team): Current implementation doesn't allow more than one Pipeline instance
* to be used simultaneously (see pipelineIterationCount metrics, for example). Explicitly
* marked this class with {@link Singleton} annotation for a while.
*/
@PipelineScoped
public class Pipeline {
private static final Logger log = Logger.getLogger(Pipeline.class.getCanonicalName());
@Inject
@NamedConfigParam("subjects_to_display")
private final Provider<Integer> subjectsToDisplay = new Provider<Integer>() {
@Override
public Integer get() {
return GroningenParams.getDefaultInstance().getSubjectsToDisplay();
}
};
private final BlockScope pipelineIterationScope;
private final Displayable displayable;
private final MonitorGroningen monitor;
private final Provider<SubjectShuffler> shuffler;
private final Hypothesizer hypothesizer;
private final ExperimentDb experimentDb;
private final Datastore datastore;
private final HistoryDatastore historyDatastore;
private final Clock clock;
private final ConfigManager configManager;
private final PipelineId pipelineId;
private final Provider<PipelineIteration> pipelineIterationProvider;
private final PipelineStageDisplayer pipelineStageDisplayer;
private final Thread pipelineThread;
private final PipelineSynchronizer pipelineSynchronizer;
/** Counts the number of pipeline iterations */
private final AtomicLong pipelineIterationCount = new AtomicLong(1);
private PipelineIteration currentIteration;
private final AtomicBoolean isKilled;
private GroningenConfig currentIterationConfig = null;
private final PipelineStageInfo pipelineStageInfo;
private static class PipelineStageDisplayer {
private PipelineIteration currentIteration;
private final String[] stages = {"Hypothesizer", "Generator", "Executor", "Validator"};
public synchronized void setCurrentIteration(PipelineIteration currentIteration) {
this.currentIteration = currentIteration;
}
@Override
public synchronized String toString() {
return stages[currentIteration != null ? currentIteration.getStage() : 0];
}
}
private synchronized void setCurrentIteration(PipelineIteration currentIteration) {
this.currentIteration = currentIteration;
}
@Inject
public Pipeline(
@Named(PipelineIterationScoped.SCOPE_NAME) BlockScope pipelineIterationScope,
Displayable displayable,
MonitorGroningen monitor,
Provider<SubjectShuffler> shuffler,
Hypothesizer hypothesizer,
Datastore datastore,
HistoryDatastore historyDatastore,
ExperimentDb experimentDb,
Clock clock,
ConfigManager configManager,
Provider<PipelineIteration> pipelineIterationProvider,
PipelineId pipelineId,
final MetricExporter metricExporter,
PipelineSynchronizer pipelineSynchronizer,
PipelineStageInfo pipelineStageInfo) {
this.pipelineIterationScope = pipelineIterationScope;
this.displayable = displayable;
this.monitor = monitor;
this.shuffler = shuffler;
this.hypothesizer = hypothesizer;
this.datastore = datastore;
this.historyDatastore = historyDatastore;
this.clock = clock;
this.configManager = configManager;
this.pipelineIterationProvider = pipelineIterationProvider;
this.pipelineId = pipelineId;
this.pipelineSynchronizer = pipelineSynchronizer;
this.pipelineThread = Thread.currentThread();
this.experimentDb = experimentDb;
this.pipelineStageInfo = pipelineStageInfo;
isKilled = new AtomicBoolean();
pipelineStageDisplayer = new PipelineStageDisplayer();
}
public PipelineId id() {
return pipelineId;
}
public synchronized PipelineIteration currentIteration() {
return currentIteration;
}
public void kill() {
isKilled.set(true);
}
public void joinPipeline() throws InterruptedException {
pipelineThread.join();
}
public void restoreState(PipelineState state) {
if (!pipelineId.equals(state.pipelineId())) {
throw new RuntimeException("trying to assign state from another pipeline");
}
experimentDb.reset(state.experimentDb());
}
public PipelineState state() {
return new PipelineState(pipelineId, configManager.queryConfig(), experimentDb);
}
public PipelineHistoryState historyState() {
List<EvaluatedSubject> evaluatedSubjects = new ArrayList<>();
for (SubjectStateBridge ssb : experimentDb.getLastExperiment().getSubjects()) {
evaluatedSubjects.add(new EvaluatedSubject(clock, ssb, FitnessScore.compute(ssb,
configManager.queryConfig()), experimentDb.getExperimentId()));
}
return new PipelineHistoryState(pipelineId,
configManager.queryConfig(),
Instant.now(),
evaluatedSubjects.toArray(new EvaluatedSubject[] {}),
experimentDb.getExperimentId());
}
/**
* Ready the monitoring.
*/
private void configureMonitoring() {
/** Export monitored variables */
monitor.monitorObject(pipelineIterationCount,
"Pipeline iteration count");
monitor.monitorObject(pipelineStageDisplayer,
"Current pipeline stage");
}
public GroningenConfig getConfig() {
return currentIterationConfig;
}
public void run() {
try {
GroningenConfig firstConfig = configManager.queryConfig();
currentIterationConfig = firstConfig;
configureMonitoring();
boolean firstIteration = true;
boolean notCompleted = true;
do {
pipelineIterationScope.enter();
try {
GroningenConfig config = configManager.queryConfig();
currentIterationConfig = config;
GroningenConfigParamsModule.nailConfigToScope(config, pipelineIterationScope);
/*
* TODO(team): Seeding these objects here is not good. In an ideal world they
* should be created per-pipeline
*/
PipelineIteration iteration = pipelineIterationProvider.get();
setCurrentIteration(iteration);
pipelineStageDisplayer.setCurrentIteration(iteration);
monitor.maxIndividuals(subjectsToDisplay.get());
pipelineStageInfo.incrementIterationAndSetState(PipelineStageState.ITERATION_START);
if (firstIteration) {
/*
* the population size within the ga engine is currently fixed once the engine is
* instantiated. Hence, there is no benefit to recalculating each time through the
* loop.
*/
try {
hypothesizer.setPopulationSize(shuffler.get().createIterator().getSubjectCount());
} catch (RuntimeException e) {
log.log(Level.SEVERE,
"unable to gather number of tasks in specified jobs. Hence unable to " +
"initialize hypothesizer. Aborting...", e);
throw e;
}
firstIteration = false;
}
notCompleted = iteration.run();
pipelineStageInfo.set(PipelineStageState.ITERATION_FINALIZATION_INPROGRESS);
try {
datastore.writePipelines(Lists.newArrayList(state()));
} catch (DatastoreException e) {
log.severe("can't write pipeline into datastore: " + e.getMessage());
}
try {
historyDatastore.writeState(historyState());
} catch (HistoryDatastoreException e) {
log.severe(
"can't write pipeline history state into history datastore: " + e.getMessage());
}
} finally {
pipelineIterationScope.exit();
}
pipelineSynchronizer.finalizeCompleteHook();
} while (notCompleted && !isKilled.get());
} catch (RuntimeException e) {
log.log(Level.SEVERE, "Fatal error", e);
throw e;
}
}
public PipelineSynchronizer getPipelineSynchronizer() {
return pipelineSynchronizer;
}
public PipelineStageInfo.ImmutablePipelineStageInfo getImmutablePipelineStageInfo() {
return pipelineStageInfo.getImmutableValueCopy();
}
public DisplayMediator getDisplayableInformationProvider() {
// TODO(sanragsood): Downcasting... DANGEROUS !
// Once the new interface is ready, decouple DisplayMediator from Displayable and directly
// inject DisplayMediator.
return (DisplayMediator) displayable;
}
}