/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* NB: This file, plus the test implementations (*Test.java) should be the only Java code that
* knows about specific tests. Please keep it that way. We need to know about specific tests here
* in order to map the XML configuration to Java classes.
*/
package adhocbenchmark;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* @author scooper
*
*/
public class BenchmarkConfiguration {
private final static String defaultTestName = "join";
private final static Map<String, QueryTestBase.Factory> testFactory = new HashMap<String, QueryTestBase.Factory>();
public static void installFactory(String name, QueryTestBase.Factory factory) {
testFactory.put(name.toLowerCase(), factory);
}
static {
installFactory("join", new JoinTest.ChainFactory());
installFactory("joinstar", new JoinTest.StarFactory());
installFactory("projection", new ProjectionTest.Factory());
installFactory("joinsp", new JoinTest.SPChainFactory());
installFactory("joinstarsp", new JoinTest.SPStarFactory());
installFactory("projectionsp", new ProjectionTest.Factory()); // Same as "projection" -- different table config
installFactory("joinmp", new JoinTest.MPChainFactory());
installFactory("joinstarmp", new JoinTest.MPStarFactory());
installFactory("projectionmp", new ProjectionTest.MPFactory());
// Install additional benchmark tests here as QueryTestBase.Factory-based factories.
}
/**
* Provide the factory associated with the given test name.
* @return factory from the map of registered factories
*/
public static QueryTestBase.Factory factoryByName(String name) {
return testFactory.get(name.toLowerCase());
}
/**
* Table configuration data from config.xml
*/
private static class ConfigurationTable {
public final String name;
public final String columnPrefix;
public final int nColumns;
public final int nVariations;
public ConfigurationTable(final String name, final String columnPrefix, int nColumns, int nVariations) {
this.name = name;
this.columnPrefix = columnPrefix;
this.nColumns = nColumns;
this.nVariations = nVariations;
}
public static ConfigurationTable fromElement(Element elem) throws ConfigurationException {
if (!elem.hasAttribute("name")) {
throw new ConfigurationException("<table> element is missing the 'name' attribute");
}
if (!elem.hasAttribute("prefix")) {
throw new ConfigurationException("<table> element is missing the 'prefix' attribute");
}
if (!elem.hasAttribute("columns")) {
throw new ConfigurationException("<table> element is missing the 'columns' attribute");
}
String name = elem.getAttribute("name");
String columnPrefix = elem.getAttribute("prefix");
int nColumns = Integer.parseInt(elem.getAttribute("columns"));
int nVariations = (elem.hasAttribute("variations")
? Integer.parseInt(elem.getAttribute("variations"))
: 1);
return new ConfigurationTable(name, columnPrefix, nColumns, nVariations);
}
}
/**
* Test configuration data from config.xml
*/
private static class ConfigurationTest {
public final String type;
public final String table;
public final int nLevels;
public ConfigurationTest(final String type, final String table, int nLevels) {
this.type = type;
this.table = table;
this.nLevels = nLevels;
}
public static ConfigurationTest fromElement(Element elem) throws ConfigurationException {
if (!elem.hasAttribute("type")) {
throw new ConfigurationException("<test> element is missing the 'type' attribute");
}
if (!elem.hasAttribute("table")) {
throw new ConfigurationException("<test> element is missing the 'table' attribute");
}
String type = elem.getAttribute("type");
String table = elem.getAttribute("table");
int nLevels = (elem.hasAttribute("levels") ? Integer.parseInt(elem.getAttribute("levels")) : 0);
return new ConfigurationTest(type, table, nLevels);
}
}
/**
* Provide the list of known test names.
* @return test name string array
*/
public static Set<String> getTestNames() {
return testFactory.keySet();
}
/**
* Provide the default test name.
* @return default test name
*/
public static String getDefaultTestName() {
// Guard against defaulting to an invalid name. That would be confusing.
// If the default test name ever gets out of sync with the set of registered test factories,
// pick its successor arbitrarily from the set, and complain about it.
if (factoryByName(defaultTestName) == null) {
for (String anyName : getTestNames()) {
System.out.println("Warning: someone forgot to hard-code a new default test name now that " +
defaultTestName + " has gone missing, so the code is arbitrarily substituting " +
anyName + ". If that works for you, consider eliminating this warning by making it the new hard-coded default.");
return anyName; // as good as any, now that defaultTestName is out of the running.
}
}
return defaultTestName;
}
/**
* Parse config.xml and produce test configuration.
*
* @param path path to configuration file
* @return test list
* @throws ConfigurationException
*/
public static List<QueryTestBase> configureTests(final String path, final String testName) throws ConfigurationException {
QueryTestBase.Factory factory = factoryByName(testName);
if (factory == null) {
throw new ConfigurationException(String.format("Specified test type '%s' has no implementation installed in BenchmarkConfiguration.java", testName));
}
List<QueryTestBase> tests = new ArrayList<QueryTestBase>();
try {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
Document doc = docBuilder.parse(new File(path));
doc.getDocumentElement().normalize();
// Read schema tables (provides variation count for tests, etc.)
Map<String, ConfigurationTable> tables = new HashMap<String, ConfigurationTable>();
Element schemaElement = (Element)doc.getElementsByTagName("schema").item(0);
NodeList tableNodes = schemaElement.getElementsByTagName("table");
for (int iTable = 0; iTable < tableNodes.getLength(); iTable++) {
ConfigurationTable table = ConfigurationTable.fromElement((Element)tableNodes.item(iTable));
tables.put(table.name, table);
}
// Read tests and build test lists mixing in data from schema read above.
Element testElement = (Element)doc.getElementsByTagName("tests").item(0);
NodeList testNodes = testElement.getElementsByTagName("test");
for (int iTest = 0; iTest < testNodes.getLength(); iTest++) {
ConfigurationTest test = ConfigurationTest.fromElement((Element)testNodes.item(iTest));
ConfigurationTable table = tables.get(test.table);
// Warning about a configuration issue that doesn't affect the current test may give the user time
// to patch the configuration file possibly before it gets read again when the affected test gets launched
// from the same shell script.
if (table == null) {
System.out.println("Configuration warning: test type '" + testName + "' references unknown table '" + test.table + "'");
continue;
}
if (testName.equalsIgnoreCase(test.type)) {
tests.add(factory.make(test.table, table.nVariations, table.columnPrefix, table.nColumns, test.nLevels));
}
}
} catch (SAXException e) {
throw new ConfigurationException("XML parser SAX exception", e);
} catch (ParserConfigurationException e) {
throw new ConfigurationException("XML parser configuration exception", e);
} catch (IOException e) {
throw new ConfigurationException("XML parser I/O exception", e);
}
if (tests.isEmpty()) {
throw new ConfigurationException(String.format("Specified test type '%s' or its table is not defined in configuration file '%s'.", testName, path));
}
return tests;
}
}