| /* |
| * 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; |
| } |
| } |