/* 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.
*/
package org.voltdb.regressionsuites.statistics;
import java.io.IOException;
import org.voltdb.BackendTarget;
import org.voltdb.VoltTable;
import org.voltdb.client.Client;
import org.voltdb.client.ProcCallException;
import org.voltdb.compiler.VoltProjectBuilder;
import org.voltdb.regressionsuites.LocalCluster;
import org.voltdb.regressionsuites.MultiConfigSuiteBuilder;
import org.voltdb.regressionsuites.RegressionSuite;
import org.voltdb_testprocs.regressionsuites.proceduredetail.ProcedureDetailTestMP;
import org.voltdb_testprocs.regressionsuites.proceduredetail.ProcedureDetailTestSP;
import junit.framework.Test;
public class TestProcedureDetails extends RegressionSuite {
public TestProcedureDetails(String name) {
super(name);
}
private final class ProcedureDetailTestConfig {
private String m_nameOfProcedureToCall;
private String m_argString;
private boolean m_expectsException;
private boolean m_singlePartition;
private boolean m_abort;
private boolean m_failure;
private long m_workingPartition = 0;
private int m_insertCount = 1;
private int m_updateCount = 1;
private int m_deleteCount = 1;
private int m_selectCount = 0;
private boolean m_twoBatch;
static final int m_singlePartitionMask = 1 << 4;
static final int m_option2BATCHMask = 1 << 3;
static final int m_optionRWMask = 1 << 2;
static final int m_optionFAILMask = 1 << 1;
static final int m_optionABORTMask = 1 << 0;
static final int m_optionCount = 5;
public ProcedureDetailTestConfig(int configValue) {
m_twoBatch = (configValue & m_option2BATCHMask) > 0;
boolean readWrite = (configValue & m_optionRWMask) > 0;
m_singlePartition = (configValue & m_singlePartitionMask) > 0;
m_failure = (configValue & m_optionFAILMask) > 0;
m_abort = (configValue & m_optionABORTMask) > 0;
if (m_singlePartition) {
// Run the single partition procedure.
m_nameOfProcedureToCall = "ProcedureDetailTestSP";
}
else {
// Run the multi-partition procedure.
m_nameOfProcedureToCall = "ProcedureDetailTestMP";
}
// If both "abort" and "twobatch" are present, the abort exception in the first
// batch will be handled so that the second batch can still run.
// But if the first batch in a multi-partition procedure failed, the procedure will abort
// anyway even if the exception is being handled:
// Multi-partition procedure xxx attempted to execute new batch after hitting EE exception in a previous batch
m_expectsException = m_failure || (m_abort && ! (m_singlePartition && m_twoBatch));
StringBuilder argBuilder = new StringBuilder();
if (m_twoBatch) {
argBuilder.append("twobatch ");
if (! m_expectsException) {
m_insertCount++;
m_deleteCount++;
m_updateCount++;
if (readWrite) {
m_selectCount++;
}
}
}
if (readWrite) {
argBuilder.append("readwrite ");
m_selectCount++;
}
if (m_failure) {
argBuilder.append("failure ");
}
if (m_abort) {
argBuilder.append("abort ");
m_insertCount++;
m_deleteCount--;
}
m_argString = argBuilder.toString();
}
public String getNameOfProcedureToCall() {
return m_nameOfProcedureToCall;
}
public String getArgumentString() {
return m_argString;
}
public boolean expectsException() {
return m_expectsException;
}
public boolean hasStatementFailure() {
return m_abort;
}
public boolean hasProcedureFailure() {
// this logic is terrible, but matches what the proc does
// the SP proc swallows the sql exception if batched
if (m_singlePartition) {
return m_failure || (m_abort && !m_twoBatch);
}
// the MP proc tries to swallow if batched, but fails
else {
return m_failure || m_abort;
}
}
public boolean isSinglePartition() {
return m_singlePartition;
}
public long getWorkingPartition() {
return m_workingPartition;
}
public void setWorkingPartition(long value) {
m_workingPartition = value;
}
public int getInsertCount() {
return m_insertCount;
}
public int getUpdateCount() {
return m_updateCount;
}
public int getDeleteCount() {
return m_deleteCount;
}
public int getSelectCount() {
return m_selectCount;
}
}
private void trivialVerification(VoltTable procedureDetail) {
assertTrue(procedureDetail.getLong("TIMESTAMP") > 0);
assertTrue(procedureDetail.getLong("MIN_EXECUTION_TIME") > 0);
assertTrue(procedureDetail.getLong("MAX_EXECUTION_TIME") > 0);
assertTrue(procedureDetail.getLong("AVG_EXECUTION_TIME") > 0);
assertTrue(procedureDetail.getLong("MIN_RESULT_SIZE") >= 0);
assertTrue(procedureDetail.getLong("MAX_RESULT_SIZE") >= 0);
assertTrue(procedureDetail.getLong("AVG_RESULT_SIZE") >= 0);
assertTrue(procedureDetail.getLong("MIN_PARAMETER_SET_SIZE") >= 0);
assertTrue(procedureDetail.getLong("MAX_PARAMETER_SET_SIZE") >= 0);
assertTrue(procedureDetail.getLong("AVG_PARAMETER_SET_SIZE") >= 0);
}
private void verifyRowsForStatement(String stmtName, long expectedInvocationCount,
ProcedureDetailTestConfig testConfig, VoltTable procedureDetail) {
for (long i = 0; i < 4; i++) {
assertTrue(procedureDetail.advanceRow());
trivialVerification(procedureDetail);
long hostId = procedureDetail.getLong("HOST_ID");
long siteId = procedureDetail.getLong("SITE_ID");
long partitionId = procedureDetail.getLong("PARTITION_ID");
assertEquals(hostId * 2 + siteId, partitionId);
assertEquals(procedureDetail.getLong("INVOCATIONS"), expectedInvocationCount);
assertEquals(procedureDetail.getLong("TIMED_INVOCATIONS"), expectedInvocationCount);
assertEquals(procedureDetail.getLong("ABORTS"), 0);
if (stmtName.equals("anInsert") && testConfig.hasStatementFailure()) {
assertEquals(procedureDetail.getLong("FAILURES"), 1);
}
else {
assertEquals(procedureDetail.getLong("FAILURES"), 0);
}
if (testConfig.isSinglePartition()) {
assertEquals(partitionId, testConfig.getWorkingPartition());
assertEquals(procedureDetail.getString("PROCEDURE"),
"org.voltdb_testprocs.regressionsuites.proceduredetail.ProcedureDetailTestSP");
break;
}
assertEquals(procedureDetail.getString("PROCEDURE"),
"org.voltdb_testprocs.regressionsuites.proceduredetail.ProcedureDetailTestMP");
}
}
private void verifyProcedureDetailResult(ProcedureDetailTestConfig testConfig, VoltTable procedureDetail) {
assertNotNull(procedureDetail);
procedureDetail.resetRowPosition();
assertTrue(procedureDetail.advanceRow());
// The first row is the <ALL> row.
trivialVerification(procedureDetail);
long hostId = procedureDetail.getLong("HOST_ID");
long siteId = procedureDetail.getLong("SITE_ID");
long partitionId = procedureDetail.getLong("PARTITION_ID");
if (testConfig.isSinglePartition()) {
assertEquals(procedureDetail.getString("PROCEDURE"),
"org.voltdb_testprocs.regressionsuites.proceduredetail.ProcedureDetailTestSP");
assertEquals(hostId * 2 + siteId, partitionId);
// See which partition this query went to.
testConfig.setWorkingPartition(partitionId);
}
else {
assertEquals(procedureDetail.getString("PROCEDURE"),
"org.voltdb_testprocs.regressionsuites.proceduredetail.ProcedureDetailTestMP");
assertEquals(hostId, 0);
assertEquals(siteId, 2);
assertEquals(partitionId, 16383);
}
assertEquals(procedureDetail.getString("STATEMENT"), "<ALL>");
assertEquals(procedureDetail.getLong("INVOCATIONS"), 1);
assertEquals(procedureDetail.getLong("TIMED_INVOCATIONS"), 1);
assertEquals(procedureDetail.getLong("FAILURES"), testConfig.hasProcedureFailure() ? 1 : 0);
if (testConfig.expectsException() && ! testConfig.hasProcedureFailure()) {
assertEquals(procedureDetail.getLong("ABORTS"), 1);
}
else {
assertEquals(procedureDetail.getLong("ABORTS"), 0);
}
if (testConfig.getDeleteCount() > 0) {
verifyRowsForStatement("aDelete", testConfig.getDeleteCount(), testConfig, procedureDetail);
}
if (testConfig.getSelectCount() > 0) {
verifyRowsForStatement("aSelect", testConfig.getSelectCount(), testConfig, procedureDetail);
}
verifyRowsForStatement("anInsert", testConfig.getInsertCount(), testConfig, procedureDetail);
verifyRowsForStatement("anUpdate", testConfig.getUpdateCount(), testConfig, procedureDetail);
}
public void testProcedureDetail() throws Exception {
Client client = getClient();
// Exhaust all the combinatorial possibilities of the *m_optionCount* options.
// In total, 32 (2^5) different scenarios are being tested here.
int maxConfigValue = 1 << ProcedureDetailTestConfig.m_optionCount;
for (int configValue = 0; configValue < maxConfigValue; configValue++) {
ProcedureDetailTestConfig testConfig = new ProcedureDetailTestConfig(configValue);
System.out.println("\n========================================================================================");
System.out.println(String.format("exec %s %d '%s'", testConfig.getNameOfProcedureToCall(),
configValue, testConfig.getArgumentString()));
boolean caughtException = false;
try {
client.callProcedure(testConfig.getNameOfProcedureToCall(),
configValue, testConfig.getArgumentString());
}
catch (ProcCallException pce) {
if (! testConfig.expectsException()) {
throw pce;
}
System.out.println("\nCaught exception as expected:\n" + pce.getMessage());
caughtException = true;
}
finally {
// Note that pass 1 as the second parameter to get incremental statistics.
VoltTable procedureDetail = client.callProcedure("@Statistics", "PROCEDUREDETAIL", 1).getResults()[0];
System.out.println(procedureDetail.toFormattedString());
verifyProcedureDetailResult(testConfig, procedureDetail);
}
// The test configuration says an exception is expected, but we did not get it.
if (testConfig.expectsException() && ! caughtException) {
fail(String.format("Expects an exception from exec %s %d '%s', but did not get it.",
testConfig.getNameOfProcedureToCall(),
configValue, testConfig.getArgumentString()));
}
}
}
/**
* Build a list of the tests that will be run when TestProcedureDetails gets run by JUnit.
* Use helper classes that are part of the RegressionSuite framework.
* This particular class runs all tests on the the local JNI backend with both
* one and two partition configurations, as well as on the hsql backend.
*
* @return The TestSuite containing all the tests to be run.
*/
static public Test suite() throws IOException {
// the suite made here will all be using the tests from this class
MultiConfigSuiteBuilder builder = new MultiConfigSuiteBuilder(TestProcedureDetails.class);
// build up a project builder for the workload
VoltProjectBuilder project = new VoltProjectBuilder();
project.setUseDDLSchema(true);
project.addLiteralSchema("CREATE TABLE ENG11890 (a INTEGER NOT NULL, b VARCHAR(10));");
project.addPartitionInfo("ENG11890", "a");
// Note that those two stored procedures have @ProcStatsOption annotations,
// every invocation of them will be sampled in the procedure detail table.
project.addProcedures(ProcedureDetailTestSP.class, ProcedureDetailTestMP.class);
// 2-node cluster, 2 sites per host, k = 0 running on the JNI backend
LocalCluster config = new LocalCluster("proceduredetail-jni.jar", 2, 2, 0, BackendTarget.NATIVE_EE_JNI);
// build the jarfile
assertTrue(config.compile(project));
// add this config to the set of tests to run
builder.addServerConfig(config);
return builder;
}
}