| /* |
| * 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.StreamTokenizer; |
| import java.io.StringReader; |
| import java.lang.reflect.Constructor; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Locale; |
| import org.apache.lucene.benchmark.byTask.PerfRunData; |
| import org.apache.lucene.benchmark.byTask.tasks.PerfTask; |
| import org.apache.lucene.benchmark.byTask.tasks.RepSumByPrefTask; |
| import org.apache.lucene.benchmark.byTask.tasks.TaskSequence; |
| |
| /** Test algorithm, as read from file */ |
| @SuppressWarnings("try") |
| public class Algorithm implements AutoCloseable { |
| |
| private TaskSequence sequence; |
| private final String[] taskPackages; |
| |
| /** |
| * Read algorithm from file Property examined: alt.tasks.packages == comma separated list of |
| * alternate package names where tasks would be searched for, when not found in the default |
| * package (that of {@link PerfTask}{@link #getClass()}). If the same task class appears in more |
| * than one package, the package indicated first in this list will be used. |
| * |
| * @param runData perf-run-data used at running the tasks. |
| * @throws Exception if errors while parsing the algorithm |
| */ |
| @SuppressWarnings("fallthrough") |
| public Algorithm(PerfRunData runData) throws Exception { |
| Config config = runData.getConfig(); |
| taskPackages = initTasksPackages(config); |
| String algTxt = config.getAlgorithmText(); |
| sequence = new TaskSequence(runData, null, null, false); |
| TaskSequence currSequence = sequence; |
| PerfTask prevTask = null; |
| StreamTokenizer stok = new StreamTokenizer(new StringReader(algTxt)); |
| stok.commentChar('#'); |
| stok.eolIsSignificant(false); |
| stok.quoteChar('"'); |
| stok.quoteChar('\''); |
| stok.ordinaryChar('/'); |
| stok.ordinaryChar('('); |
| stok.ordinaryChar(')'); |
| boolean colonOk = false; |
| boolean isDisableCountNextTask = false; // only for primitive tasks |
| currSequence.setDepth(0); |
| |
| while (stok.nextToken() != StreamTokenizer.TT_EOF) { |
| switch (stok.ttype) { |
| case StreamTokenizer.TT_WORD: |
| String s = stok.sval; |
| Constructor<? extends PerfTask> cnstr = |
| taskClass(config, s).asSubclass(PerfTask.class).getConstructor(PerfRunData.class); |
| PerfTask task = cnstr.newInstance(runData); |
| task.setAlgLineNum(stok.lineno()); |
| task.setDisableCounting(isDisableCountNextTask); |
| isDisableCountNextTask = false; |
| currSequence.addTask(task); |
| if (task instanceof RepSumByPrefTask) { |
| stok.nextToken(); |
| String prefix = stok.sval; |
| if (prefix == null || prefix.length() == 0) { |
| throw new Exception("named report prefix problem - " + stok.toString()); |
| } |
| ((RepSumByPrefTask) task).setPrefix(prefix); |
| } |
| // check for task param: '(' someParam ')' |
| stok.nextToken(); |
| if (stok.ttype != '(') { |
| stok.pushBack(); |
| } else { |
| // get params, for tasks that supports them - allow recursive parenthetical expressions |
| stok.eolIsSignificant(true); // Allow params tokenizer to keep track of line number |
| StringBuilder params = new StringBuilder(); |
| stok.nextToken(); |
| if (stok.ttype != ')') { |
| int count = 1; |
| BALANCED_PARENS: |
| while (true) { |
| switch (stok.ttype) { |
| case StreamTokenizer.TT_NUMBER: |
| { |
| params.append(stok.nval); |
| break; |
| } |
| case StreamTokenizer.TT_WORD: |
| { |
| params.append(stok.sval); |
| break; |
| } |
| case StreamTokenizer.TT_EOF: |
| { |
| throw new RuntimeException("Unexpexted EOF: - " + stok.toString()); |
| } |
| case '"': |
| case '\'': |
| { |
| params.append((char) stok.ttype); |
| // re-escape delimiters, if any |
| params.append( |
| stok.sval.replaceAll("" + (char) stok.ttype, "\\\\" + (char) stok.ttype)); |
| params.append((char) stok.ttype); |
| break; |
| } |
| case '(': |
| { |
| params.append((char) stok.ttype); |
| ++count; |
| break; |
| } |
| case ')': |
| { |
| if (--count >= 1) { // exclude final closing parenthesis |
| params.append((char) stok.ttype); |
| } else { |
| break BALANCED_PARENS; |
| } |
| break; |
| } |
| default: |
| { |
| params.append((char) stok.ttype); |
| } |
| } |
| stok.nextToken(); |
| } |
| } |
| stok.eolIsSignificant(false); |
| String prm = params.toString().trim(); |
| if (prm.length() > 0) { |
| task.setParams(prm); |
| } |
| } |
| |
| // --------------------------------------- |
| colonOk = false; |
| prevTask = task; |
| break; |
| |
| default: |
| char c = (char) stok.ttype; |
| |
| switch (c) { |
| case ':': |
| if (!colonOk) throw new Exception("colon unexpexted: - " + stok.toString()); |
| colonOk = false; |
| // get repetitions number |
| stok.nextToken(); |
| if ((char) stok.ttype == '*') { |
| ((TaskSequence) prevTask).setRepetitions(TaskSequence.REPEAT_EXHAUST); |
| } else { |
| if (stok.ttype != StreamTokenizer.TT_NUMBER) { |
| throw new Exception("expected repetitions number or XXXs: - " + stok.toString()); |
| } else { |
| double num = stok.nval; |
| stok.nextToken(); |
| if (stok.ttype == StreamTokenizer.TT_WORD && stok.sval.equals("s")) { |
| ((TaskSequence) prevTask).setRunTime(num); |
| } else { |
| stok.pushBack(); |
| ((TaskSequence) prevTask).setRepetitions((int) num); |
| } |
| } |
| } |
| // check for rate specification (ops/min) |
| stok.nextToken(); |
| if (stok.ttype != ':') { |
| stok.pushBack(); |
| } else { |
| // get rate number |
| stok.nextToken(); |
| if (stok.ttype != StreamTokenizer.TT_NUMBER) |
| throw new Exception("expected rate number: - " + stok.toString()); |
| // check for unit - min or sec, sec is default |
| stok.nextToken(); |
| if (stok.ttype != '/') { |
| stok.pushBack(); |
| ((TaskSequence) prevTask).setRate((int) stok.nval, false); // set rate per sec |
| } else { |
| stok.nextToken(); |
| if (stok.ttype != StreamTokenizer.TT_WORD) |
| throw new Exception("expected rate unit: 'min' or 'sec' - " + stok.toString()); |
| String unit = stok.sval.toLowerCase(Locale.ROOT); |
| if ("min".equals(unit)) { |
| ((TaskSequence) prevTask).setRate((int) stok.nval, true); // set rate per min |
| } else if ("sec".equals(unit)) { |
| ((TaskSequence) prevTask).setRate((int) stok.nval, false); // set rate per sec |
| } else { |
| throw new Exception("expected rate unit: 'min' or 'sec' - " + stok.toString()); |
| } |
| } |
| } |
| colonOk = false; |
| break; |
| |
| case '{': |
| case '[': |
| // a sequence |
| // check for sequence name |
| String name = null; |
| stok.nextToken(); |
| if (stok.ttype != '"') { |
| stok.pushBack(); |
| } else { |
| name = stok.sval; |
| if (stok.ttype != '"' || name == null || name.length() == 0) { |
| throw new Exception("sequence name problem - " + stok.toString()); |
| } |
| } |
| // start the sequence |
| TaskSequence seq2 = new TaskSequence(runData, name, currSequence, c == '['); |
| currSequence.addTask(seq2); |
| currSequence = seq2; |
| colonOk = false; |
| break; |
| |
| case '&': |
| if (currSequence.isParallel()) { |
| throw new Exception("Can only create background tasks within a serial task"); |
| } |
| stok.nextToken(); |
| final int deltaPri; |
| if (stok.ttype != StreamTokenizer.TT_NUMBER) { |
| stok.pushBack(); |
| deltaPri = 0; |
| } else { |
| // priority |
| deltaPri = (int) stok.nval; |
| } |
| |
| if (prevTask == null) { |
| throw new Exception("& was unexpected"); |
| } else if (prevTask.getRunInBackground()) { |
| throw new Exception("double & was unexpected"); |
| } else { |
| prevTask.setRunInBackground(deltaPri); |
| } |
| break; |
| |
| case '>': |
| currSequence.setNoChildReport(); /* intentional fallthrough */ |
| case '}': |
| case ']': |
| // end sequence |
| colonOk = true; |
| prevTask = currSequence; |
| currSequence = currSequence.getParent(); |
| break; |
| |
| case '-': |
| isDisableCountNextTask = true; |
| break; |
| } // switch(c) |
| break; |
| } // switch(stok.ttype) |
| } |
| |
| if (sequence != currSequence) { |
| throw new Exception("Unmatched sequences"); |
| } |
| |
| // remove redundant top level enclosing sequences |
| while (sequence.isCollapsable() && sequence.getRepetitions() == 1 && sequence.getRate() == 0) { |
| ArrayList<PerfTask> t = sequence.getTasks(); |
| if (t != null && t.size() == 1) { |
| PerfTask p = t.get(0); |
| if (p instanceof TaskSequence) { |
| sequence = (TaskSequence) p; |
| continue; |
| } |
| } |
| break; |
| } |
| } |
| |
| private String[] initTasksPackages(Config config) { |
| String alts = config.get("alt.tasks.packages", null); |
| String dfltPkg = PerfTask.class.getPackage().getName(); |
| if (alts == null) { |
| return new String[] {dfltPkg}; |
| } |
| ArrayList<String> pkgs = new ArrayList<>(); |
| pkgs.add(dfltPkg); |
| for (String alt : alts.split(",")) { |
| pkgs.add(alt); |
| } |
| return pkgs.toArray(new String[0]); |
| } |
| |
| private Class<?> taskClass(Config config, String taskName) throws ClassNotFoundException { |
| for (String pkg : taskPackages) { |
| try { |
| return Class.forName(pkg + '.' + taskName + "Task"); |
| } catch (ClassNotFoundException e) { |
| // failed in this package, might succeed in the next one... |
| } |
| } |
| // can only get here if failed to instantiate |
| throw new ClassNotFoundException( |
| taskName + " not found in packages " + Arrays.toString(taskPackages)); |
| } |
| |
| @Override |
| public String toString() { |
| String newline = System.getProperty("line.separator"); |
| StringBuilder sb = new StringBuilder(); |
| sb.append(sequence.toString()); |
| sb.append(newline); |
| return sb.toString(); |
| } |
| |
| /** Execute this algorithm */ |
| public void execute() throws Exception { |
| try { |
| sequence.runAndMaybeStats(true); |
| } finally { |
| sequence.close(); |
| } |
| } |
| |
| /** |
| * Expert: for test purposes, return all tasks participating in this algorithm. |
| * |
| * @return all tasks participating in this algorithm. |
| */ |
| public ArrayList<PerfTask> extractTasks() { |
| ArrayList<PerfTask> res = new ArrayList<>(); |
| extractTasks(res, sequence); |
| return res; |
| } |
| |
| private void extractTasks(ArrayList<PerfTask> extrct, TaskSequence seq) { |
| if (seq == null) return; |
| extrct.add(seq); |
| ArrayList<PerfTask> t = sequence.getTasks(); |
| if (t == null) return; |
| for (final PerfTask p : t) { |
| if (p instanceof TaskSequence) { |
| extractTasks(extrct, (TaskSequence) p); |
| } else { |
| extrct.add(p); |
| } |
| } |
| } |
| |
| @Override |
| public void close() throws Exception { |
| sequence.close(); |
| } |
| } |