/* 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.validator;
import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import org.arbeitspferde.groningen.config.GroningenConfig;
import org.arbeitspferde.groningen.config.PipelineIterationScoped;
import org.arbeitspferde.groningen.display.MonitorGroningen;
import org.arbeitspferde.groningen.experimentdb.CommandLine;
import org.arbeitspferde.groningen.experimentdb.Experiment;
import org.arbeitspferde.groningen.experimentdb.ExperimentDb;
import org.arbeitspferde.groningen.experimentdb.PauseTime;
import org.arbeitspferde.groningen.experimentdb.ResourceMetric;
import org.arbeitspferde.groningen.experimentdb.SubjectRestart;
import org.arbeitspferde.groningen.experimentdb.SubjectStateBridge;
import org.arbeitspferde.groningen.profiling.ProfilingRunnable;
import org.arbeitspferde.groningen.utility.Clock;
import org.arbeitspferde.groningen.utility.Metric;
import org.arbeitspferde.groningen.utility.MetricExporter;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
/**
* The Validator pipeline stage performs validation on subject group members to prevent grossly
* degenerate individuals from polluting the population. For example, subjects that are flapping
* excessively are invalidated to strongly signal to the {@link Hypothesizer} that it should not be
* propagated into the next generation.
*/
@PipelineIterationScoped
public class Validator extends ProfilingRunnable {
/** Logger for this class */
private static final Logger logger = Logger.getLogger(Validator.class.getCanonicalName());
/** The Experimental Database */
private final ExperimentDb experimentDb;
private final GroningenConfig config;
private final MetricExporter metricExporter;
private final AtomicLong invalidDueToRestartThresholdCrossed = new AtomicLong(0);
private final AtomicLong invalidDueToNeverStarting = new AtomicLong(0);
private final AtomicLong invalidDueToCommandLineMismatch = new AtomicLong(0);
private final AtomicLong invalidDueToRemoval = new AtomicLong(0);
@Inject
public Validator(final Clock clock, final MonitorGroningen monitor, final ExperimentDb e,
final GroningenConfig config,
final MetricExporter metricExporter) {
super(clock, monitor);
experimentDb = e;
this.config = config;
this.metricExporter = metricExporter;
}
@Override
public void profiledRun(GroningenConfig config) {
Experiment lastExperiment = null;
lastExperiment = experimentDb.getLastExperiment();
if (lastExperiment == null) {
logger.warning("Experiments do not exist. Skipping Validator stage.");
} else {
for (final SubjectStateBridge subject : lastExperiment.getSubjects()) {
final PauseTime pauseTime = subject.getPauseTime();
final ResourceMetric resourceMetric = subject.getResourceMetric();
if (invalidSubject(subject)) {
subject.markInvalid();
pauseTime.invalidate();
resourceMetric.invalidate();
} else {
subject.markValid();
}
}
}
}
/**
* Returns true iff the subject is invalid.
*
* Note, default subject cannot be invalid. Always returns {@code false}.
*/
private boolean invalidSubject(final SubjectStateBridge bridge) {
Preconditions.checkNotNull(bridge.getAssociatedSubject());
if (bridge.getAssociatedSubject().isDefault()) {
return false;
}
boolean invalid = false;
final SubjectRestart subjectRestart = bridge.getSubjectRestart();
final StringBuilder subjectSignature = new StringBuilder();
subjectSignature.append("[Subject Id: ");
subjectSignature.append(bridge.getIdOfObject());
subjectSignature.append("| Serving Address ");
subjectSignature.append(bridge.getAssociatedSubject().getServingAddress());
subjectSignature.append("]");
// Excessively flapping subjects are invalid because they're really bad for user facing services
// even if we are only talking about a single subject.
if (subjectRestart.restartThresholdCrossed(config)) {
invalid = true;
invalidDueToRestartThresholdCrossed.incrementAndGet();
logger.warning(
String.format("%s invalidated for too many illegal restarts.", subjectSignature));
}
// A subject is invalid when it did not run in production, even though it is never actually
// had a chance to be fairly scored. Alternatives are to propagate it into the next generation
// without mutation or simply run it later after the initial set of experimental subjects run.
// We believe invalidating it to be the lesser of these "evils".
if (subjectRestart.didNotRun()) {
invalid = true;
invalidDueToNeverStarting.incrementAndGet();
logger.warning(String.format("%s invalidated for having not run.", subjectSignature));
}
// Check if the subject's command line matches with the authoritative's command-line strings.
final CommandLine commandLine = bridge.getCommandLine();
final List<String> commandLineStrings = bridge.getCommandLineStrings();
if (commandLine == null) {
logger.warning(String.format("%s invalidated for lacking an associated CommandLine object.",
subjectSignature));
} else if (commandLineStrings.isEmpty()) {
logger.warning(String.format("%s invalidated for lacking associated command line string.",
subjectSignature));
} else {
final String cls = commandLine.toArgumentString().trim();
for (final String commandLineString : commandLineStrings) {
if ((cls != null) && (!commandLineString.contains(cls))) {
invalid = true;
invalidDueToCommandLineMismatch.incrementAndGet();
logger.warning(String.format(
"%s invalidated due to command line mismatch: »%s« versus »%s«.",
subjectSignature, commandLineString, cls));
}
}
}
// Subjects that were removed from an experiment are invalid
if (bridge.wasRemoved()) {
invalid = true;
invalidDueToRemoval.incrementAndGet();
}
return invalid;
}
@Override
public void startUp() {
logger.info("Initializing Validator.");
// TODO(team): This will need to be fixed such that metrics can be made pipeline.specific.
metricExporter.register(
"invalidate_due_to_restart",
"Counts the number of invalidations due to crossing restart threshold.",
Metric.make(invalidDueToRestartThresholdCrossed));
metricExporter.register(
"invalidate_due_to_did_not_run",
"Counts the number of invalidations due to a subject failing to not starting.",
Metric.make(invalidDueToNeverStarting));
metricExporter.register(
"invalidate_due_to_command_line_mismatch",
"Counts the number of invalidations due to command-line string mismatch.",
Metric.make(invalidDueToCommandLineMismatch));
metricExporter.register(
"invalidate_due_to_removal",
"Counts the number of invalidations due to removal from the experiment.",
Metric.make(invalidDueToRemoval));
}
}