blob: 821db38956412d03dbad59f90b220caa97c125b0 [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.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();
}
}