blob: 713f5a67a5157d5da845389b01d590dc7328a511 [file] [log] [blame]
package org.apache.cassandra.stress.settings;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*
*/
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Function;
import org.apache.commons.math3.distribution.ExponentialDistribution;
import org.apache.commons.math3.distribution.NormalDistribution;
import org.apache.commons.math3.distribution.UniformRealDistribution;
import org.apache.commons.math3.distribution.WeibullDistribution;
import org.apache.commons.math3.random.JDKRandomGenerator;
import org.apache.cassandra.stress.generate.*;
/**
* For selecting a mathematical distribution
*/
public class OptionDistribution extends Option
{
public static final Function<String, DistributionFactory> BUILDER = new Function<String, DistributionFactory>()
{
public DistributionFactory apply(String s)
{
return get(s);
}
};
private static final Pattern FULL = Pattern.compile("(~?)([A-Z]+)\\((.+)\\)", Pattern.CASE_INSENSITIVE);
private static final Pattern ARGS = Pattern.compile("[^,]+");
final String prefix;
private String spec;
private final String defaultSpec;
private final String description;
private final boolean required;
public OptionDistribution(String prefix, String defaultSpec, String description)
{
this(prefix, defaultSpec, description, defaultSpec == null);
}
public OptionDistribution(String prefix, String defaultSpec, String description, boolean required)
{
this.prefix = prefix;
this.defaultSpec = defaultSpec;
this.description = description;
this.required = required;
}
@Override
public boolean accept(String param)
{
if (!param.toLowerCase().startsWith(prefix))
return false;
spec = param.substring(prefix.length());
return true;
}
public static DistributionFactory get(String spec)
{
Matcher m = FULL.matcher(spec);
if (!m.matches())
throw new IllegalArgumentException("Illegal distribution specification: " + spec);
boolean inverse = m.group(1).equals("~");
String name = m.group(2);
Impl impl = LOOKUP.get(name.toLowerCase());
if (impl == null)
throw new IllegalArgumentException("Illegal distribution type: " + name);
List<String> params = new ArrayList<>();
m = ARGS.matcher(m.group(3));
while (m.find())
params.add(m.group());
DistributionFactory factory = impl.getFactory(params);
return inverse ? new InverseFactory(factory) : factory;
}
public DistributionFactory get()
{
return spec != null ? get(spec) : defaultSpec != null ? get(defaultSpec) : null;
}
@Override
public boolean happy()
{
return !required || spec != null;
}
public String longDisplay()
{
return shortDisplay() + ": " + description;
}
@Override
public List<String> multiLineDisplay()
{
return Arrays.asList(
GroupedOptions.formatMultiLine("EXP(min..max)", "An exponential distribution over the range [min..max]"),
GroupedOptions.formatMultiLine("EXTREME(min..max,shape)", "An extreme value (Weibull) distribution over the range [min..max]"),
GroupedOptions.formatMultiLine("QEXTREME(min..max,shape,quantas)", "An extreme value, split into quantas, within which the chance of selection is uniform"),
GroupedOptions.formatMultiLine("GAUSSIAN(min..max,stdvrng)", "A gaussian/normal distribution, where mean=(min+max)/2, and stdev is (mean-min)/stdvrng"),
GroupedOptions.formatMultiLine("GAUSSIAN(min..max,mean,stdev)", "A gaussian/normal distribution, with explicitly defined mean and stdev"),
GroupedOptions.formatMultiLine("UNIFORM(min..max)", "A uniform distribution over the range [min, max]"),
GroupedOptions.formatMultiLine("FIXED(val)", "A fixed distribution, always returning the same value"),
"Preceding the name with ~ will invert the distribution, e.g. ~exp(1..10) will yield 10 most, instead of least, often",
"Aliases: extr, qextr, gauss, normal, norm, weibull"
);
}
boolean setByUser()
{
return spec != null;
}
boolean present()
{
return setByUser() || defaultSpec != null;
}
@Override
public String shortDisplay()
{
return (defaultSpec != null ? "[" : "") + prefix + "DIST(?)" + (defaultSpec != null ? "]" : "");
}
private static final Map<String, Impl> LOOKUP;
static
{
final Map<String, Impl> lookup = new HashMap<>();
lookup.put("exp", new ExponentialImpl());
lookup.put("extr", new ExtremeImpl());
lookup.put("qextr", new QuantizedExtremeImpl());
lookup.put("extreme", lookup.get("extr"));
lookup.put("qextreme", lookup.get("qextr"));
lookup.put("weibull", lookup.get("weibull"));
lookup.put("gaussian", new GaussianImpl());
lookup.put("normal", lookup.get("gaussian"));
lookup.put("gauss", lookup.get("gaussian"));
lookup.put("norm", lookup.get("gaussian"));
lookup.put("uniform", new UniformImpl());
lookup.put("fixed", new FixedImpl());
LOOKUP = lookup;
}
// factory builders
private static interface Impl
{
public DistributionFactory getFactory(List<String> params);
}
public static long parseLong(String value)
{
long multiplier = 1;
value = value.trim().toLowerCase();
switch (value.charAt(value.length() - 1))
{
case 'b':
multiplier *= 1000;
case 'm':
multiplier *= 1000;
case 'k':
multiplier *= 1000;
value = value.substring(0, value.length() - 1);
}
return Long.parseLong(value) * multiplier;
}
private static final class GaussianImpl implements Impl
{
@Override
public DistributionFactory getFactory(List<String> params)
{
if (params.size() > 3 || params.size() < 1)
throw new IllegalArgumentException("Invalid parameter list for gaussian distribution: " + params);
try
{
String[] bounds = params.get(0).split("\\.\\.+");
final long min = parseLong(bounds[0]);
final long max = parseLong(bounds[1]);
final double mean, stdev;
if (params.size() == 3)
{
mean = Double.parseDouble(params.get(1));
stdev = Double.parseDouble(params.get(2));
}
else
{
final double stdevsToEdge = params.size() == 1 ? 3d : Double.parseDouble(params.get(1));
mean = (min + max) / 2d;
stdev = ((max - min) / 2d) / stdevsToEdge;
}
if (min == max)
return new FixedFactory(min);
return new GaussianFactory(min, max, mean, stdev);
} catch (Exception ignore)
{
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
}
}
}
private static final class ExponentialImpl implements Impl
{
@Override
public DistributionFactory getFactory(List<String> params)
{
if (params.size() != 1)
throw new IllegalArgumentException("Invalid parameter list for gaussian distribution: " + params);
try
{
String[] bounds = params.get(0).split("\\.\\.+");
final long min = parseLong(bounds[0]);
final long max = parseLong(bounds[1]);
if (min == max)
return new FixedFactory(min);
ExponentialDistribution findBounds = new ExponentialDistribution(1d);
// max probability should be roughly equal to accuracy of (max-min) to ensure all values are visitable,
// over entire range, but this results in overly skewed distribution, so take sqrt
final double mean = (max - min) / findBounds.inverseCumulativeProbability(1d - Math.sqrt(1d/(max-min)));
return new ExpFactory(min, max, mean);
} catch (Exception ignore)
{
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
}
}
}
private static final class ExtremeImpl implements Impl
{
@Override
public DistributionFactory getFactory(List<String> params)
{
if (params.size() != 2)
throw new IllegalArgumentException("Invalid parameter list for extreme (Weibull) distribution: " + params);
try
{
String[] bounds = params.get(0).split("\\.\\.+");
final long min = parseLong(bounds[0]);
final long max = parseLong(bounds[1]);
if (min == max)
return new FixedFactory(min);
final double shape = Double.parseDouble(params.get(1));
WeibullDistribution findBounds = new WeibullDistribution(shape, 1d);
// max probability should be roughly equal to accuracy of (max-min) to ensure all values are visitable,
// over entire range, but this results in overly skewed distribution, so take sqrt
final double scale = (max - min) / findBounds.inverseCumulativeProbability(1d - Math.sqrt(1d/(max-min)));
return new ExtremeFactory(min, max, shape, scale);
} catch (Exception ignore)
{
throw new IllegalArgumentException("Invalid parameter list for extreme (Weibull) distribution: " + params);
}
}
}
private static final class QuantizedExtremeImpl implements Impl
{
@Override
public DistributionFactory getFactory(List<String> params)
{
if (params.size() != 3)
throw new IllegalArgumentException("Invalid parameter list for quantized extreme (Weibull) distribution: " + params);
try
{
String[] bounds = params.get(0).split("\\.\\.+");
final long min = parseLong(bounds[0]);
final long max = parseLong(bounds[1]);
final double shape = Double.parseDouble(params.get(1));
final int quantas = Integer.parseInt(params.get(2));
WeibullDistribution findBounds = new WeibullDistribution(shape, 1d);
// max probability should be roughly equal to accuracy of (max-min) to ensure all values are visitable,
// over entire range, but this results in overly skewed distribution, so take sqrt
final double scale = (max - min) / findBounds.inverseCumulativeProbability(1d - Math.sqrt(1d/(max-min)));
if (min == max)
return new FixedFactory(min);
return new QuantizedExtremeFactory(min, max, shape, scale, quantas);
} catch (Exception ignore)
{
throw new IllegalArgumentException("Invalid parameter list for quantized extreme (Weibull) distribution: " + params);
}
}
}
private static final class UniformImpl implements Impl
{
@Override
public DistributionFactory getFactory(List<String> params)
{
if (params.size() != 1)
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
try
{
String[] bounds = params.get(0).split("\\.\\.+");
final long min = parseLong(bounds[0]);
final long max = parseLong(bounds[1]);
if (min == max)
return new FixedFactory(min);
return new UniformFactory(min, max);
} catch (Exception ignore)
{
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
}
}
}
private static final class FixedImpl implements Impl
{
@Override
public DistributionFactory getFactory(List<String> params)
{
if (params.size() != 1)
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
try
{
final long key = parseLong(params.get(0));
return new FixedFactory(key);
} catch (Exception ignore)
{
throw new IllegalArgumentException("Invalid parameter list for uniform distribution: " + params);
}
}
}
private static final class InverseFactory implements DistributionFactory
{
final DistributionFactory wrapped;
private InverseFactory(DistributionFactory wrapped)
{
this.wrapped = wrapped;
}
public Distribution get()
{
return new DistributionInverted(wrapped.get());
}
}
// factories
private static final class ExpFactory implements DistributionFactory
{
final long min, max;
final double mean;
private ExpFactory(long min, long max, double mean)
{
this.min = min;
this.max = max;
this.mean = mean;
}
@Override
public Distribution get()
{
return new DistributionOffsetApache(new ExponentialDistribution(new JDKRandomGenerator(), mean, ExponentialDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY), min, max);
}
}
private static class ExtremeFactory implements DistributionFactory
{
final long min, max;
final double shape, scale;
private ExtremeFactory(long min, long max, double shape, double scale)
{
this.min = min;
this.max = max;
this.shape = shape;
this.scale = scale;
}
@Override
public Distribution get()
{
return new DistributionOffsetApache(new WeibullDistribution(new JDKRandomGenerator(), shape, scale, WeibullDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY), min, max);
}
}
private static final class QuantizedExtremeFactory extends ExtremeFactory
{
final int quantas;
private QuantizedExtremeFactory(long min, long max, double shape, double scale, int quantas)
{
super(min, max, shape, scale);
this.quantas = quantas;
}
@Override
public Distribution get()
{
return new DistributionQuantized(new DistributionOffsetApache(new WeibullDistribution(new JDKRandomGenerator(), shape, scale, WeibullDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY), min, max), quantas);
}
}
private static final class GaussianFactory implements DistributionFactory
{
final long min, max;
final double mean, stdev;
private GaussianFactory(long min, long max, double mean, double stdev)
{
this.min = min;
this.max = max;
this.stdev = stdev;
this.mean = mean;
}
@Override
public Distribution get()
{
return new DistributionBoundApache(new NormalDistribution(new JDKRandomGenerator(), mean, stdev, NormalDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY), min, max);
}
}
private static final class UniformFactory implements DistributionFactory
{
final long min, max;
private UniformFactory(long min, long max)
{
this.min = min;
this.max = max;
}
@Override
public Distribution get()
{
return new DistributionBoundApache(new UniformRealDistribution(new JDKRandomGenerator(), min, max + 1), min, max);
}
}
private static final class FixedFactory implements DistributionFactory
{
final long key;
private FixedFactory(long key)
{
this.key = key;
}
@Override
public Distribution get()
{
return new DistributionFixed(key);
}
}
@Override
public int hashCode()
{
return prefix.hashCode();
}
@Override
public boolean equals(Object that)
{
return super.equals(that) && ((OptionDistribution) that).prefix.equals(this.prefix);
}
}