/*
*
* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
* *
* * 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.
* *
* * For more information: http://www.orientechnologies.com
*
*/
package com.orientechnologies.orient.stresstest;
import com.orientechnologies.orient.client.remote.OStorageRemote;
import com.orientechnologies.orient.stresstest.workload.OWorkload;
import java.io.Console;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* This is the parser of the command line arguments passed with the invocation of OStressTester. It contains a static method that -
* given the arguments - returns a OStressTester object.
*
* @author Andrea Iacono
*/
public class OStressTesterCommandLineParser {
public static final String TEMP_DATABASE_NAME = "stress-test-db-";
public static final String CONSOLE_REMOTE_PASSWORD_PROMPT = "OrientDB Server (%s:%d) - Please insert the root password to create the test database: ";
public final static String OPTION_CONCURRENCY = "c";
public final static String OPTION_MODE = "m";
public final static String OPTION_WORKLOAD = "w";
public final static String OPTION_TRANSACTIONS = "tx";
public final static String OPTION_DELAY = "delay";
public final static String OPTION_KEEP_DATABASE_AFTER_TEST = "k";
public final static String OPTION_OUTPUT_FILE = "o";
public static final String OPTION_PLOCAL_PATH = "d";
public final static String OPTION_LOAD_BALANCING = "lb";
public final static String OPTION_DBNAME = "db";
public final static String OPTION_CHECK_DATABASE = "chk";
public final static String OPTION_ROOT_PASSWORD = "root-password";
public static final String ERROR_OPENING_CONSOLE =
"An error has occurred opening the console. Please supply the root password as the -" + OPTION_ROOT_PASSWORD + " parameter.";
public final static String OPTION_REMOTE_IP = "remote-ip";
public final static String OPTION_HA_METRICS = "ha-metrics";
public static final String COMMAND_LINE_PARSER_MISSING_REMOTE_IP =
"The mode is [" + OStressTester.OMode.REMOTE + "] but the param --" + OPTION_REMOTE_IP + " wasn't passed.";
public final static String OPTION_REMOTE_PORT = "remote-port";
public final static String MAIN_OPTIONS =
OPTION_MODE + OPTION_CONCURRENCY + OPTION_WORKLOAD + OPTION_TRANSACTIONS + OPTION_DELAY + OPTION_OUTPUT_FILE
+ OPTION_PLOCAL_PATH + OPTION_KEEP_DATABASE_AFTER_TEST + OPTION_CHECK_DATABASE + OPTION_LOAD_BALANCING + OPTION_DBNAME;
public static final String SYNTAX =
"StressTester " + "\n\t-m mode (can be any of these: [plocal|memory|remote|distributed] )" + "\n\t-w workloads"
+ "\n\t-c concurrency-level" + "\n\t-x operations-per-transaction" + "\n\t-o result-output-file"
+ "\n\t-d database-directory" + "\n\t-k true|false" + "\n\t-chk true|false" + "\n\t--root-password rootPassword"
+ "\n\t--remote-ip ipOrHostname" + "\n\t--remote-port portNumber" + "\n\t-lb load-balancing-strategy" + "\n\t-db db-name"
+ "\n";
static final String COMMAND_LINE_PARSER_INVALID_NUMBER = "Invalid %s number [%s].";
static final String COMMAND_LINE_PARSER_LESSER_THAN_ZERO_NUMBER = "The %s value must be greater than 0.";
static final String COMMAND_LINE_PARSER_INVALID_MODE = "Invalid mode [%s].";
static final String COMMAND_LINE_PARSER_INVALID_OPTION = "Invalid option [%s]";
static final String COMMAND_LINE_PARSER_EXPECTED_VALUE = "Expected value after argument [%s]";
static final String COMMAND_LINE_PARSER_INVALID_REMOTE_PORT_NUMBER = "Invalid remote port [%d]. The port number has to be lesser than 65536.";
static final String COMMAND_LINE_PARSER_MODE_PARAM_MANDATORY = "The mode param [-m] is mandatory.";
static final String COMMAND_LINE_PARSER_NOT_EXISTING_OUTPUT_DIRECTORY = "The directory where to write the resultOutputFile [%s] doesn't exist.";
static final String COMMAND_LINE_PARSER_NOT_EXISTING_PLOCAL_PATH = "The plocal directory (param -d) doesn't exist [%s].";
static final String COMMAND_LINE_PARSER_NO_WRITE_PERMISSION_OUTPUT_FILE = "You don't have the permissions for writing on directory [%s] the resultOutputFile.";
static final String COMMAND_LINE_PARSER_NO_WRITE_PERMISSION_PLOCAL_PATH = "You don't have the permissions for writing on plocal directory [%s].";
static final String COMMAND_LINE_PARSER_PLOCAL_PATH_IS_NOT_DIRECTORY = "The plocal path [%s] is not a directory.";
/**
* builds a StressTester object using the command line arguments
*
* @param args
*
* @return
*
* @throws Exception
*/
public static OStressTester getStressTester(String[] args) throws Exception {
final Map<String, String> options = checkOptions(readOptions(args));
final OStressTesterSettings settings = new OStressTesterSettings();
settings.dbName = TEMP_DATABASE_NAME + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
if (options.get(OPTION_DBNAME) != null)
settings.dbName = options.get(OPTION_DBNAME);
settings.mode = OStressTester.OMode.valueOf(options.get(OPTION_MODE).toUpperCase());
settings.rootPassword = options.get(OPTION_ROOT_PASSWORD);
settings.resultOutputFile = options.get(OPTION_OUTPUT_FILE);
settings.plocalPath = options.get(OPTION_PLOCAL_PATH);
settings.operationsPerTransaction = getNumber(options.get(OPTION_TRANSACTIONS), "transactions");
settings.delay = getNumber(options.get(OPTION_DELAY), "delay");
settings.concurrencyLevel = getNumber(options.get(OPTION_CONCURRENCY), "concurrency");
settings.remoteIp = options.get(OPTION_REMOTE_IP);
settings.haMetrics = options.get(OPTION_HA_METRICS) != null ? Boolean.parseBoolean(options.get(OPTION_HA_METRICS)) : false;
settings.workloadCfg = options.get(OPTION_WORKLOAD);
settings.keepDatabaseAfterTest = options.get(OPTION_KEEP_DATABASE_AFTER_TEST) != null ?
Boolean.parseBoolean(options.get(OPTION_KEEP_DATABASE_AFTER_TEST)) :
false;
settings.remotePort = 2424;
settings.checkDatabase = Boolean.parseBoolean(options.get(OPTION_CHECK_DATABASE));
if (options.get(OPTION_LOAD_BALANCING) != null)
settings.loadBalancing = OStorageRemote.CONNECTION_STRATEGY.valueOf(options.get(OPTION_LOAD_BALANCING).toUpperCase());
if (settings.plocalPath != null) {
if (settings.plocalPath.endsWith(File.separator)) {
settings.plocalPath = settings.plocalPath.substring(0, settings.plocalPath.length() - File.separator.length());
}
File plocalFile = new File(settings.plocalPath);
if (!plocalFile.exists()) {
throw new IllegalArgumentException(String.format(COMMAND_LINE_PARSER_NOT_EXISTING_PLOCAL_PATH, settings.plocalPath));
}
if (!plocalFile.canWrite()) {
throw new IllegalArgumentException(String.format(COMMAND_LINE_PARSER_NO_WRITE_PERMISSION_PLOCAL_PATH, settings.plocalPath));
}
if (!plocalFile.isDirectory()) {
throw new IllegalArgumentException(String.format(COMMAND_LINE_PARSER_PLOCAL_PATH_IS_NOT_DIRECTORY, settings.plocalPath));
}
}
if (settings.resultOutputFile != null) {
File outputFile = new File(settings.resultOutputFile);
if (outputFile.exists()) {
outputFile.delete();
}
File parentFile = outputFile.getParentFile();
// if the filename does not contain a path (both relative and absolute)
if (parentFile == null) {
parentFile = new File(".");
}
if (!parentFile.exists()) {
throw new IllegalArgumentException(
String.format(COMMAND_LINE_PARSER_NOT_EXISTING_OUTPUT_DIRECTORY, parentFile.getAbsoluteFile()));
}
if (!parentFile.canWrite()) {
throw new IllegalArgumentException(
String.format(COMMAND_LINE_PARSER_NO_WRITE_PERMISSION_OUTPUT_FILE, parentFile.getAbsoluteFile()));
}
}
if (options.get(OPTION_REMOTE_PORT) != null) {
settings.remotePort = getNumber(options.get(OPTION_REMOTE_PORT), "remotePort");
if (settings.remotePort > 65535) {
throw new IllegalArgumentException(String.format(COMMAND_LINE_PARSER_INVALID_REMOTE_PORT_NUMBER, settings.remotePort));
}
}
if (settings.mode == OStressTester.OMode.DISTRIBUTED) {
throw new IllegalArgumentException(String.format("OMode [%s] not yet supported.", settings.mode));
}
if (settings.mode == OStressTester.OMode.REMOTE && settings.remoteIp == null) {
throw new IllegalArgumentException(COMMAND_LINE_PARSER_MISSING_REMOTE_IP);
}
if (settings.rootPassword == null && settings.mode == OStressTester.OMode.REMOTE) {
Console console = System.console();
if (console != null) {
settings.rootPassword = String
.valueOf(console.readPassword(String.format(CONSOLE_REMOTE_PASSWORD_PROMPT, settings.remoteIp, settings.remotePort)));
} else {
throw new Exception(ERROR_OPENING_CONSOLE);
}
}
final List<OWorkload> workloads = parseWorkloads(settings.workloadCfg);
final ODatabaseIdentifier databaseIdentifier = new ODatabaseIdentifier(settings);
return new OStressTester(workloads, databaseIdentifier, settings);
}
private static List<OWorkload> parseWorkloads(final String workloadConfig) {
if (workloadConfig == null || workloadConfig.isEmpty())
throw new IllegalArgumentException("Workload parameter is mandatory. Syntax: <workload-name:workload-params>");
final List<OWorkload> result = new ArrayList<OWorkload>();
final String[] parts = workloadConfig.split(",");
for (String part : parts) {
String workloadName;
String workloadParams;
final int pos = part.indexOf(":");
if (pos > -1) {
workloadName = part.substring(0, pos);
workloadParams = part.substring(pos + 1);
} else {
workloadName = part;
workloadParams = null;
}
final OWorkload workload = OStressTester.getWorkloadFactory().get(workloadName);
if (workload == null)
throw new IllegalArgumentException(
"Workload '" + workloadName + "' is not configured. Use one of the following: " + OStressTester.getWorkloadFactory()
.getRegistered());
workload.parseParameters(workloadParams);
result.add(workload);
}
return result;
}
private static int getNumber(final String value, final String option) throws IllegalArgumentException {
if (value == null)
return 0;
try {
int val = Integer.parseInt(value);
if (val < 0) {
throw new IllegalArgumentException(String.format(COMMAND_LINE_PARSER_LESSER_THAN_ZERO_NUMBER, option));
}
return val;
} catch (NumberFormatException ex) {
throw new IllegalArgumentException(String.format(COMMAND_LINE_PARSER_INVALID_NUMBER, option, value));
}
}
private static Map<String, String> checkOptions(Map<String, String> options) throws IllegalArgumentException {
if (options.get(OPTION_MODE) == null) {
throw new IllegalArgumentException(String.format(COMMAND_LINE_PARSER_MODE_PARAM_MANDATORY));
}
options = setDefaultIfNotPresent(options, OPTION_MODE, OStressTester.OMode.PLOCAL.name());
options = setDefaultIfNotPresent(options, OPTION_CONCURRENCY, "4");
options = setDefaultIfNotPresent(options, OPTION_TRANSACTIONS, "0");
options = setDefaultIfNotPresent(options, OPTION_WORKLOAD, "CRUD:C25000R25000U25000D25000");
try {
OStressTester.OMode.valueOf(options.get(OPTION_MODE).toUpperCase());
} catch (IllegalArgumentException ex) {
throw new IllegalArgumentException(String.format(COMMAND_LINE_PARSER_INVALID_MODE, options.get(OPTION_MODE)));
}
return options;
}
private static Map<String, String> setDefaultIfNotPresent(Map<String, String> options, String option, String value)
throws IllegalArgumentException {
if (!options.containsKey(option)) {
System.out.println(String.format("WARNING: '%s' option not found. Defaulting to %s.", option, value));
options.put(option, value);
}
return options;
}
private static Map<String, String> readOptions(final String[] args) throws IllegalArgumentException {
final Map<String, String> options = new HashMap<String, String>();
// reads arguments from command line
for (int i = 0; i < args.length; i++) {
// an argument cannot be shorter than one char
if (args[i].length() < 2) {
throw new IllegalArgumentException(String.format(COMMAND_LINE_PARSER_INVALID_OPTION, args[i]));
}
switch (args[i].charAt(0)) {
case '-':
if (args.length - 1 == i) {
throw new IllegalArgumentException((String.format(COMMAND_LINE_PARSER_EXPECTED_VALUE, args[i])));
}
String option = args[i].substring(1);
if (option.startsWith("-")) {
option = option.substring(1);
} else {
if (!MAIN_OPTIONS.contains(option)) {
throw new IllegalArgumentException((String.format(COMMAND_LINE_PARSER_INVALID_OPTION, args[i])));
}
}
options.put(option, args[i + 1]);
// jumps to the next switch
i++;
break;
}
}
return options;
}
}