/*
* Copyright 2014 OpenRQ Team
*
* 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.
*/
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import net.fec.openrq.SourceBlockDecodingTest;
import net.fec.openrq.SourceBlockEncodingTest;
import net.fec.openrq.parameters.ParameterChecker;
import net.fec.openrq.util.io.SafeStandardStreams;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.results.format.ResultFormat;
import org.openjdk.jmh.results.format.ResultFormatFactory;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.format.OutputFormat;
import org.openjdk.jmh.runner.format.OutputFormatFactory;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.VerboseMode;
import com.beust.jcommander.IValueValidator;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
/**
*
*/
public final class EncodecodeBenchmarkRunner {
private static final PrintStream STDOUT = SafeStandardStreams.safeSTDOUT();
private static final int DEFAULT_SYMBOL_SIZE = 1500; // in bytes
private static final int DEFAULT_SYMBOL_OVERHEAD = 0;
private static final int DEFAULT_FORKS = 1;
public static void main(String[] args) {
final JCommander jCommander = new JCommander();
try {
final InputOptions options = parseOptions(jCommander, args);
final List<Integer> Ks = options.numSourceSymbolsList();
final int T = options.symbolSize();
final List<Integer> sos = options.symbolOverheadList();
final boolean runEncoding = options.runEncodingBenchmarks();
final boolean runDecoding = options.runDecodingBenchmarks();
final int forks = options.forks();
final VerboseMode verbMode = getVerboseMode(options);
final List<RunResult> results = new ArrayList<>();
final PrintWriter pw = new PrintWriter(STDOUT, true); // true means autoflush is on
final ResultFormat resultFormat = ResultFormatFactory.getInstance(ResultFormatType.TEXT, pw);
final long startNanos = System.nanoTime();
STDOUT.println("Starting benchmark runners...");
if (runEncoding) {
for (int K : Ks) {
final int F = deriveDataLength(K, T);
STDOUT.println("Running encoding benchmark for K = " + K);
Runner encRunner = newEncodingRunner(F, K, forks, verbMode);
results.addAll(encRunner.run());
}
}
if (runDecoding) {
for (int K : Ks) {
final int F = deriveDataLength(K, T);
for (int symbover : sos) {
STDOUT.println("Running decoding benchmark for K = " + K + " and symbol overhead = " + symbover);
Runner decRunner = newDecodingRunner(F, K, symbover, forks, verbMode);
results.addAll(decRunner.run());
}
}
}
final long ellapsed = System.nanoTime() - startNanos;
STDOUT.println("Done. Benchmark time: " + TimeUnit.NANOSECONDS.toSeconds(ellapsed) + "s");
STDOUT.println();
STDOUT.println("Benchmark results:");
resultFormat.writeOut(results);
pw.flush();
}
catch (ParameterException e) {
STDOUT.println(e.getMessage());
STDOUT.println();
printUsage(jCommander);
}
catch (RunnerException e) {
e.printStackTrace(STDOUT);
}
}
private static int deriveDataLength(int srcsymbs, int symbsize) {
return srcsymbs * symbsize;
}
private static VerboseMode getVerboseMode(InputOptions options) {
if (options.extraVerbose) {
return VerboseMode.EXTRA;
}
else if (options.verbose) {
return VerboseMode.NORMAL;
}
else {
return VerboseMode.SILENT;
}
}
private static Runner newEncodingRunner(int datalen, int srcsymbs, int forks, VerboseMode mode) {
Options opt = new OptionsBuilder()
.include(".*" + SourceBlockEncodingTest.class.getSimpleName() + ".*")
.param("datalen", datalen + "")
.param("srcsymbs", srcsymbs + "")
.forks(forks)
.build();
return new Runner(opt, getOutputFormat(mode));
}
private static Runner newDecodingRunner(int datalen, int srcsymbs, int symbover, int forks, VerboseMode mode) {
Options opt = new OptionsBuilder()
.include(".*" + SourceBlockDecodingTest.class.getSimpleName() + ".*")
.param("datalen", datalen + "")
.param("srcsymbs", srcsymbs + "")
.param("symbover", symbover + "")
.forks(forks)
.build();
return new Runner(opt, getOutputFormat(mode));
}
private static OutputFormat getOutputFormat(VerboseMode mode) {
return OutputFormatFactory.createFormatInstance(STDOUT, mode);
}
private static InputOptions parseOptions(JCommander jCommander, String[] args) throws ParameterException {
final InputOptions options = new InputOptions();
jCommander.addObject(options);
jCommander.parse(args);
return options;
}
private static void printUsage(JCommander jCommander) {
StringBuilder usageBuilder = new StringBuilder();
jCommander.usage(usageBuilder);
STDOUT.println(usageBuilder);
}
private static final class InputOptions {
@Parameter(names = {"-k", "-K", "--srcsymbs"},
description = "Space separated list of K values (K is the number of source symbols in one block)",
required = true,
variableArity = true,
validateValueWith = NumSourceSymbolsValidator.class)
private final List<String> numSourceSymbolsList = new ArrayList<>();
@Parameter(names = {"-t", "-T", "--symbsize"},
description = "The size of each symbol in number of bytes",
validateValueWith = SymbolSizeValidator.class)
private int symbolSize = DEFAULT_SYMBOL_SIZE;
@Parameter(names = {"-o", "-O", "--symbover"},
description = "Space separated list of symbol overhead values (used only in decoding benchmarks)",
variableArity = true,
validateValueWith = SymbolOverheadsValidator.class)
private final List<String> symbolOverheadList = defaultSymbolOverheadList();
@Parameter(names = {"-e", "-E", "--encodingonly"},
description = "Only run encoding benchmarks (unless option \"-d\" is used as well)")
private boolean runEncodingOnly = false;
@Parameter(names = {"-d", "-D", "--decodingonly"},
description = "Only run decoding benchmarks (unless option \"-e\" is used as well)")
private boolean runDecodingOnly = false;
@Parameter(names = {"-f", "--forks"},
description = "How many times to fork a single benchmark",
variableArity = true,
validateValueWith = NonNegativeValidator.class)
private int forks = DEFAULT_FORKS;
@Parameter(names = {"-v", "-V"},
description = "Details about the current benchmark will be printed to the standard output")
private boolean verbose = false;
@Parameter(names = {"-vv", "-VV"},
description = "Extra details about the current benchmark will be printed to the standard output")
private boolean extraVerbose = false;
private static List<String> defaultSymbolOverheadList() {
List<String> list = new ArrayList<>();
list.add(DEFAULT_SYMBOL_OVERHEAD + "");
return list;
}
List<Integer> numSourceSymbolsList() {
final List<Integer> list = new ArrayList<>(numSourceSymbolsList.size());
for (String K : numSourceSymbolsList) {
list.add(Integer.valueOf(K));
}
Collections.sort(list);
return list;
}
int symbolSize() {
return symbolSize;
}
List<Integer> symbolOverheadList() {
final List<Integer> list = new ArrayList<>(symbolOverheadList.size());
for (String symbover : symbolOverheadList) {
list.add(Integer.valueOf(symbover));
}
Collections.sort(list);
return list;
}
boolean runEncodingBenchmarks() {
return runEncodingOnly || !runDecodingOnly;
}
boolean runDecodingBenchmarks() {
return runDecodingOnly || !runEncodingOnly;
}
int forks() {
return forks;
}
}
public static final class NumSourceSymbolsValidator implements IValueValidator<List<String>> {
@Override
public void validate(@SuppressWarnings("unused") String name, List<String> value) throws ParameterException {
try {
for (String Kstr : value) {
final int K = Integer.parseInt(Kstr);
if (ParameterChecker.isNumSourceSymbolsPerBlockOutOfBounds(K)) {
throw new ParameterException(String.format(
"Number of source symbols (%d) must be within [%d, %d]",
K,
ParameterChecker.minNumSourceSymbolsPerBlock(),
ParameterChecker.maxNumSourceSymbolsPerBlock()));
}
}
}
catch (NumberFormatException e) {
throw new ParameterException("Invalid number of source symbols: " + e.getMessage());
}
}
}
public static final class SymbolSizeValidator implements IValueValidator<Integer> {
@Override
public void validate(@SuppressWarnings("unused") String name, Integer value) throws ParameterException {
if (ParameterChecker.isSymbolSizeOutOfBounds(value)) {
throw new ParameterException(String.format(
"Symbol size (%d) must be within [%d, %d] bytes",
value,
ParameterChecker.minSymbolSize(),
ParameterChecker.maxSymbolSize()));
}
}
}
public static final class SymbolOverheadsValidator implements IValueValidator<List<String>> {
private static final NonNegativeValidator NON_NEGATIVE_VALIDATOR = new NonNegativeValidator();
@Override
public void validate(String name, List<String> value) throws ParameterException {
try {
for (String symboverStr : value) {
NON_NEGATIVE_VALIDATOR.validate(name, Integer.valueOf(symboverStr));
}
}
catch (NumberFormatException e) {
throw new ParameterException("Invalid symbol overhead: " + e.getMessage());
}
}
}
public static final class NonNegativeValidator implements IValueValidator<Integer> {
@Override
public void validate(String name, Integer value) throws ParameterException {
if (value < 0) {
throw new ParameterException("Option \"" + name + "\": number must be non-negative");
}
}
}
private EncodecodeBenchmarkRunner() {
// not instantiable
}
}