/* 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.experimentdb.jvmflags; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.Logger; /** * An immutable class to represent a set of {@link JvmFlag}. * * It provides, among other things, semantic validation of the state of JVM flags at-large * throughout the system. */ public class JvmFlagSet { private static final Logger log = Logger.getLogger(JvmFlagSet.class.getCanonicalName()); private final Map<JvmFlag, Long> finalValues; /** * Non-instantiatiable; please use {@link Builder}. */ private JvmFlagSet(final Map<JvmFlag, Long> values) { finalValues = new TreeMap<>(Preconditions.checkNotNull( values, "values may not be null.")); } /** * Retrieve the value associated with a given {@link JvmFlag}. * * @param flag The flag name. * @return The value. */ public long getValue(final JvmFlag flag) { Preconditions.checkNotNull(flag, "flag may not be null."); final Long value = finalValues.get(flag); Preconditions.checkNotNull(value, "value should not be null: %s", flag); return value; } @Override public String toString() { return Objects.toStringHelper(JvmFlagSet.class) .addValue(finalValues) .toString(); } /** * A mechanism for conveniently building {@link JvmFlagSet}. * */ public static class Builder { private final Map<JvmFlag, Long> assignedValues = new EnumMap<>(JvmFlag.class); private static final Map<JvmFlag, Long> defaultValues = new EnumMap<>(JvmFlag.class); static { // TODO(team): Evaluate whether more sensible defaults should be set. /* TODO(team): Address this sagely wisdom from team member: * this seems like it needs to be documented. * It also seems like we want a hard and soft validation here. On that allows use * of the defaults and one that doesn't. I could be swayed away from this stance * though. I think we are losing some checks by moving from explicit args to * methods toward map based flag passing and I'd like for say the hypothesizer at * least to use the strict version so we don't miss locations where someone forgot * to add the read/write of the new flag. * * I don't think the defaults were used outside of test. So perhaps exposing an * @VFT allowDefaults() method might give you the flexibility to make testing easy * without having us potentially lose flag values. */ for (final JvmFlag flag : JvmFlag.values()) { defaultValues.put(flag, 0L); } } /** * Non-instantiable; please use {@link JvmFlagSet#builder()}; */ private Builder() { } /** * Assign a value to a given {@link JvmFlag}. * * @param flag The flag. * @param value The value. * @throws IllegalArgumentException in case a nonsensical value is provided. */ public Builder withValue(final JvmFlag flag, final long value) throws IllegalArgumentException { Preconditions.checkNotNull(flag, "flag may not be null."); flag.validate(value); if (assignedValues.get(flag) != null) { log.warning(String.format( "Flag %s is being reset in value; this is probably not intended.", flag)); } assignedValues.put(flag, value); return this; } /** * Yield the {@link JvmFlagSet} after performing validations and such. * * All unassigned values will receive a system-determined default value. * * @return The final {@link JvmFlagSet}. */ public JvmFlagSet build() { final Map<JvmFlag, Long> emission = new EnumMap<>(JvmFlag.class); for (final JvmFlag flag : defaultValues.keySet()) { final Long defaultValue = defaultValues.get(flag); final Long assignedValue = assignedValues.get(flag); final Long actualValue = Objects.firstNonNull(assignedValue, defaultValue); emission.put(flag, actualValue); } validate(); return new JvmFlagSet(emission); } /** * Check the validity of all arguments. */ private void validate() { /* * TODO(team): While migrating code to this, audit places for sanity/semantic checking and * migrate tests to here. * * TODO(team): Evaluate fixing invalid value conditions. */ if (areAssigned(JvmFlag.HEAP_SIZE, JvmFlag.MAX_NEW_SIZE)) { final long heapMinimum = assignedValues.get(JvmFlag.HEAP_SIZE); final long maxNewSize = assignedValues.get(JvmFlag.MAX_NEW_SIZE); if (maxNewSize >= heapMinimum) { log.severe(String.format( "XXX FIXME XXX - heapMinimum %s should never be smaller than maxNewSize %s.", heapMinimum, maxNewSize)); } } final List<JvmFlag> gcModes = ImmutableList.<JvmFlag>builder() .add(JvmFlag.USE_CONC_MARK_SWEEP_GC) .add(JvmFlag.USE_PARALLEL_GC) .add(JvmFlag.USE_PARALLEL_OLD_GC) .add(JvmFlag.USE_SERIAL_GC) .build(); final List<JvmFlag> setModes = Lists.newArrayList(); for (final JvmFlag flag : gcModes) { if (areAssigned(flag)) { final long value = assignedValues.get(flag); if (value > 0) { setModes.add(flag); } } } final int setModesCount = setModes.size(); if (setModesCount == 0) { log.severe("XXX FIXME XXX - At least one GC mode must be set."); } else if (setModesCount > 1) { final String warning = new StringBuilder() .append("Only one GC mode may be set: ") .append(Joiner.on(" ").join(setModes)) .append(".") .toString(); log.severe(String.format("XXX FIXME XXX - %s", warning)); } } /** * Check that the given {@link JvmFlag} are explicitly set. * * @param flags The flags to check. * @return True if all the enumerated flags were explicitly set. */ private boolean areAssigned(final JvmFlag ...flags) { for (final JvmFlag flag : flags) { if (!assignedValues.containsKey(flag)) { return false; } } return true; } } /** * Produce a new {@link Builder}. * * @return The empty {@link Builder}. */ public static Builder builder() { return new Builder(); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final JvmFlagSet that = (JvmFlagSet) o; return finalValues.equals(that.finalValues); } @Override public int hashCode() { return finalValues.hashCode(); } }