blob: 74709156c9a537a06b5f5d6648236c15b9f43f97 [file] [log] [blame]
/*
* 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.
*/
package org.apache.lucene.benchmark.byTask.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
/**
* Perf run configuration properties.
*
* <p>Numeric property containing ":", e.g. "10:100:5" is interpreted as array of numeric values. It
* is extracted once, on first use, and maintain a round number to return the appropriate value.
*
* <p>The config property "work.dir" tells where is the root of docs data dirs and indexes dirs. It
* is set to either of:
*
* <ul>
* <li>value supplied for it in the alg file;
* <li>otherwise, value of System property "benchmark.work.dir";
* <li>otherwise, "work".
* </ul>
*/
public class Config {
// For tests, if verbose is not turned on, don't print the props.
private static final String DEFAULT_PRINT_PROPS = System.getProperty("tests.verbose", "true");
private static final String NEW_LINE = System.getProperty("line.separator");
private int roundNumber = 0;
private Properties props;
private HashMap<String, Object> valByRound = new HashMap<>();
private HashMap<String, String> colForValByRound = new HashMap<>();
private String algorithmText;
/**
* Read both algorithm and config properties.
*
* @param algReader from where to read algorithm and config properties.
* @throws IOException If there is a low-level I/O error.
*/
public Config(Reader algReader) throws IOException {
// read alg file to array of lines
ArrayList<String> lines = new ArrayList<>();
BufferedReader r = new BufferedReader(algReader);
int lastConfigLine = 0;
for (String line = r.readLine(); line != null; line = r.readLine()) {
lines.add(line);
if (line.indexOf('=') > 0) {
lastConfigLine = lines.size();
}
}
r.close();
// copy props lines to string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < lastConfigLine; i++) {
sb.append(lines.get(i));
sb.append(NEW_LINE);
}
// read props from string
this.props = new Properties();
props.load(new StringReader(sb.toString()));
// make sure work dir is set properly
if (props.get("work.dir") == null) {
props.setProperty("work.dir", System.getProperty("benchmark.work.dir", "work"));
}
if (Boolean.valueOf(props.getProperty("print.props", DEFAULT_PRINT_PROPS)).booleanValue()) {
printProps();
}
// copy algorithm lines
sb = new StringBuilder();
for (int i = lastConfigLine; i < lines.size(); i++) {
sb.append(lines.get(i));
sb.append(NEW_LINE);
}
algorithmText = sb.toString();
}
/**
* Create config without algorithm - useful for a programmatic perf test.
*
* @param props - configuration properties.
*/
public Config(Properties props) {
this.props = props;
if (Boolean.valueOf(props.getProperty("print.props", DEFAULT_PRINT_PROPS)).booleanValue()) {
printProps();
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void printProps() {
System.out.println("------------> config properties:");
List<String> propKeys = new ArrayList(props.keySet());
Collections.sort(propKeys);
for (final String propName : propKeys) {
System.out.println(propName + " = " + props.getProperty(propName));
}
System.out.println("-------------------------------");
}
/**
* Return a string property.
*
* @param name name of property.
* @param dflt default value.
* @return a string property.
*/
public String get(String name, String dflt) {
String vals[] = (String[]) valByRound.get(name);
if (vals != null) {
return vals[roundNumber % vals.length];
}
// done if not by round
String sval = props.getProperty(name, dflt);
if (sval == null) {
return null;
}
if (sval.indexOf(":") < 0) {
return sval;
} else if (sval.indexOf(":\\") >= 0 || sval.indexOf(":/") >= 0) {
// this previously messed up absolute path names on Windows. Assuming
// there is no real value that starts with \ or /
return sval;
}
// first time this prop is extracted by round
int k = sval.indexOf(":");
String colName = sval.substring(0, k);
sval = sval.substring(k + 1);
colForValByRound.put(name, colName);
vals = propToStringArray(sval);
valByRound.put(name, vals);
return vals[roundNumber % vals.length];
}
/**
* Set a property. Note: once a multiple values property is set, it can no longer be modified.
*
* @param name name of property.
* @param value either single or multiple property value (multiple values are separated by ":")
*/
public void set(String name, String value) throws Exception {
if (valByRound.get(name) != null) {
throw new Exception("Cannot modify a multi value property!");
}
props.setProperty(name, value);
}
/**
* Return an int property. If the property contain ":", e.g. "10:100:5", it is interpreted as
* array of ints. It is extracted once, on first call to get() it, and a by-round-value is
* returned.
*
* @param name name of property
* @param dflt default value
* @return a int property.
*/
public int get(String name, int dflt) {
// use value by round if already parsed
int vals[] = (int[]) valByRound.get(name);
if (vals != null) {
return vals[roundNumber % vals.length];
}
// done if not by round
String sval = props.getProperty(name, "" + dflt);
if (sval.indexOf(":") < 0) {
return Integer.parseInt(sval);
}
// first time this prop is extracted by round
int k = sval.indexOf(":");
String colName = sval.substring(0, k);
sval = sval.substring(k + 1);
colForValByRound.put(name, colName);
vals = propToIntArray(sval);
valByRound.put(name, vals);
return vals[roundNumber % vals.length];
}
/**
* Return a double property. If the property contain ":", e.g. "10:100:5", it is interpreted as
* array of doubles. It is extracted once, on first call to get() it, and a by-round-value is
* returned.
*
* @param name name of property
* @param dflt default value
* @return a double property.
*/
public double get(String name, double dflt) {
// use value by round if already parsed
double vals[] = (double[]) valByRound.get(name);
if (vals != null) {
return vals[roundNumber % vals.length];
}
// done if not by round
String sval = props.getProperty(name, "" + dflt);
if (sval.indexOf(":") < 0) {
return Double.parseDouble(sval);
}
// first time this prop is extracted by round
int k = sval.indexOf(":");
String colName = sval.substring(0, k);
sval = sval.substring(k + 1);
colForValByRound.put(name, colName);
vals = propToDoubleArray(sval);
valByRound.put(name, vals);
return vals[roundNumber % vals.length];
}
/**
* Return a boolean property. If the property contain ":", e.g. "true.true.false", it is
* interpreted as array of booleans. It is extracted once, on first call to get() it, and a
* by-round-value is returned.
*
* @param name name of property
* @param dflt default value
* @return a int property.
*/
public boolean get(String name, boolean dflt) {
// use value by round if already parsed
boolean vals[] = (boolean[]) valByRound.get(name);
if (vals != null) {
return vals[roundNumber % vals.length];
}
// done if not by round
String sval = props.getProperty(name, "" + dflt);
if (sval.indexOf(":") < 0) {
return Boolean.valueOf(sval).booleanValue();
}
// first time this prop is extracted by round
int k = sval.indexOf(":");
String colName = sval.substring(0, k);
sval = sval.substring(k + 1);
colForValByRound.put(name, colName);
vals = propToBooleanArray(sval);
valByRound.put(name, vals);
return vals[roundNumber % vals.length];
}
/**
* Increment the round number, for config values that are extracted by round number.
*
* @return the new round number.
*/
public int newRound() {
roundNumber++;
StringBuilder sb =
new StringBuilder("--> Round ").append(roundNumber - 1).append("-->").append(roundNumber);
// log changes in values
if (valByRound.size() > 0) {
sb.append(": ");
for (final Map.Entry<String, Object> entry : valByRound.entrySet()) {
final String name = entry.getKey();
Object a = entry.getValue();
if (a instanceof int[]) {
int ai[] = (int[]) a;
int n1 = (roundNumber - 1) % ai.length;
int n2 = roundNumber % ai.length;
sb.append(" ").append(name).append(":").append(ai[n1]).append("-->").append(ai[n2]);
} else if (a instanceof double[]) {
double ad[] = (double[]) a;
int n1 = (roundNumber - 1) % ad.length;
int n2 = roundNumber % ad.length;
sb.append(" ").append(name).append(":").append(ad[n1]).append("-->").append(ad[n2]);
} else if (a instanceof String[]) {
String ad[] = (String[]) a;
int n1 = (roundNumber - 1) % ad.length;
int n2 = roundNumber % ad.length;
sb.append(" ").append(name).append(":").append(ad[n1]).append("-->").append(ad[n2]);
} else {
boolean ab[] = (boolean[]) a;
int n1 = (roundNumber - 1) % ab.length;
int n2 = roundNumber % ab.length;
sb.append(" ").append(name).append(":").append(ab[n1]).append("-->").append(ab[n2]);
}
}
}
System.out.println();
System.out.println(sb.toString());
System.out.println();
return roundNumber;
}
private String[] propToStringArray(String s) {
if (s.indexOf(":") < 0) {
return new String[] {s};
}
ArrayList<String> a = new ArrayList<>();
StringTokenizer st = new StringTokenizer(s, ":");
while (st.hasMoreTokens()) {
String t = st.nextToken();
a.add(t);
}
return a.toArray(new String[a.size()]);
}
// extract properties to array, e.g. for "10:100:5" return int[]{10,100,5}.
private int[] propToIntArray(String s) {
if (s.indexOf(":") < 0) {
return new int[] {Integer.parseInt(s)};
}
ArrayList<Integer> a = new ArrayList<>();
StringTokenizer st = new StringTokenizer(s, ":");
while (st.hasMoreTokens()) {
String t = st.nextToken();
a.add(Integer.valueOf(t));
}
int res[] = new int[a.size()];
for (int i = 0; i < a.size(); i++) {
res[i] = a.get(i).intValue();
}
return res;
}
// extract properties to array, e.g. for "10.7:100.4:-2.3" return int[]{10.7,100.4,-2.3}.
private double[] propToDoubleArray(String s) {
if (s.indexOf(":") < 0) {
return new double[] {Double.parseDouble(s)};
}
ArrayList<Double> a = new ArrayList<>();
StringTokenizer st = new StringTokenizer(s, ":");
while (st.hasMoreTokens()) {
String t = st.nextToken();
a.add(Double.valueOf(t));
}
double res[] = new double[a.size()];
for (int i = 0; i < a.size(); i++) {
res[i] = a.get(i).doubleValue();
}
return res;
}
// extract properties to array, e.g. for "true:true:false" return boolean[]{true,false,false}.
private boolean[] propToBooleanArray(String s) {
if (s.indexOf(":") < 0) {
return new boolean[] {Boolean.valueOf(s).booleanValue()};
}
ArrayList<Boolean> a = new ArrayList<>();
StringTokenizer st = new StringTokenizer(s, ":");
while (st.hasMoreTokens()) {
String t = st.nextToken();
a.add(Boolean.valueOf(t));
}
boolean res[] = new boolean[a.size()];
for (int i = 0; i < a.size(); i++) {
res[i] = a.get(i).booleanValue();
}
return res;
}
/** @return names of params set by round, for reports title */
public String getColsNamesForValsByRound() {
if (colForValByRound.size() == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (final String colName : colForValByRound.values()) {
sb.append(' ').append(colName);
}
return sb.toString();
}
/** @return values of params set by round, for reports lines. */
public String getColsValuesForValsByRound(int roundNum) {
if (colForValByRound.size() == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (final Map.Entry<String, String> entry : colForValByRound.entrySet()) {
String colName = entry.getValue();
String template = " " + colName;
if (roundNum < 0) {
// just append blanks
sb.append(Format.formatPaddLeft("-", template));
} else {
String valByRoundName = entry.getKey();
// append actual values, for that round
Object a = valByRound.get(valByRoundName);
if (a instanceof int[]) {
int ai[] = (int[]) a;
int n = roundNum % ai.length;
sb.append(Format.format(ai[n], template));
} else if (a instanceof double[]) {
double ad[] = (double[]) a;
int n = roundNum % ad.length;
sb.append(Format.format(2, ad[n], template));
} else if (a instanceof String[]) {
String ad[] = (String[]) a;
int n = roundNum % ad.length;
sb.append(ad[n]);
} else {
boolean ab[] = (boolean[]) a;
int n = roundNum % ab.length;
sb.append(Format.formatPaddLeft("" + ab[n], template));
}
}
}
return sb.toString();
}
/** @return the round number. */
public int getRoundNumber() {
return roundNumber;
}
/** @return Returns the algorithmText. */
public String getAlgorithmText() {
return algorithmText;
}
}