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