/* 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.generator; import com.google.inject.Inject; import org.arbeitspferde.groningen.config.GroningenConfig; import org.arbeitspferde.groningen.config.GroningenConfig.ClusterConfig; import org.arbeitspferde.groningen.config.GroningenConfig.SubjectGroupConfig; import org.arbeitspferde.groningen.config.NamedConfigParam; import org.arbeitspferde.groningen.config.PipelineIterationScoped; import org.arbeitspferde.groningen.proto.Params.GroningenParams; import org.arbeitspferde.groningen.subject.ServingAddressGenerator; import org.arbeitspferde.groningen.subject.Subject; import org.arbeitspferde.groningen.subject.SubjectGroup; import org.arbeitspferde.groningen.subject.SubjectManipulator; import org.arbeitspferde.groningen.utility.PermanentFailure; import org.arbeitspferde.groningen.utility.TemporaryFailure; import org.uncommons.maths.random.MersenneTwisterRNG; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; /** * Walks through a list of experimental{@link Subject}s that we can then associate with * {@link SubjectStateBridge} objects in the {@link Generator}. * * The order of the {@link List} of {@link Subject}s is randomized to minimize experimental bias. */ @PipelineIterationScoped public class SubjectShuffler { /** Logger for this class */ private static final Logger log = Logger.getLogger(SubjectShuffler.class.getCanonicalName()); /** * Iterator class for list of subjects that also provides precached subjectsCount value */ public static class SubjectIterator implements Iterator<Subject> { private final Iterator<Subject> subjectIterator; /** * Cache the total number of subjects in the shuffler so it can be queried without walking the * iterator */ private final int subjectCount; public SubjectIterator(final Iterator<Subject> subjectIterator, final int subjectCount) { this.subjectIterator = subjectIterator; this.subjectCount = subjectCount; } @Override public boolean hasNext() { return subjectIterator.hasNext(); } @Override public Subject next() { return subjectIterator.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } public int getSubjectCount() { return subjectCount; } } @Inject @NamedConfigParam("subject_group_number_of_shuffles") private final int shuffleCount = GroningenParams.getDefaultInstance().getSubjectGroupNumberOfShuffles(); /** The random number generator we are using */ private final Random rng = new MersenneTwisterRNG(); private final GroningenConfig config; private final SubjectManipulator manipulator; private final ServingAddressGenerator servingAddressBuilder; @Inject public SubjectShuffler(final GroningenConfig config, final SubjectManipulator manipulator, final ServingAddressGenerator servingAddressBuilder) { this.config = config; this.manipulator = manipulator; this.servingAddressBuilder = servingAddressBuilder; } /** * Initialize the list of {@link Subject}s by querying {@link GroningenConfig} and interacting * with the cluster environment by using the {@link SubjectManipulator} class. */ public SubjectIterator createIterator() { List<Subject> subjects = null; for (final ClusterConfig clusterConfig : config.getClusterConfigs()) { final String clusterName = clusterConfig.getName(); for (SubjectGroupConfig groupConfig : clusterConfig.getSubjectGroupConfigs()) { final String groupName = groupConfig.getName(); final String userName = groupConfig.getUser(); /* * TODO(team): Allow this to be provided via Guice's Assisted Inject or factory to ease * testing. */ final SubjectGroup subjectGroup = new SubjectGroup(clusterName, groupName, userName, groupConfig, servingAddressBuilder); try { subjects = subjectGroup.initialize(manipulator, subjects); } catch (final PermanentFailure | TemporaryFailure e) { throw new RuntimeException("Could not create a list of subjects.", e); } } } if (subjects != null) { if (subjects.size() > 1) { // We are shuffling multiple times to ensure a good quality shuffle. This may be overkill // because we're using a good quality random number generator. However, we only do this // once per experiment and the shuffle runs in linear time on a relatively small list. So, // why not shuffle a multiple times? for (int i = 0; i < shuffleCount; i++) { Collections.shuffle(subjects, rng); } } return new SubjectIterator(subjects.iterator(), subjects.size()); } else { log.log(Level.SEVERE, "Empty iterator intialized; this should never happen!", new Throwable()); throw new IllegalStateException("Empty iterator intialized; this should never happen!"); } } }