/* 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)); } }