package org.voltdb.regressionsuites; import java.util.Map; import java.util.TreeMap; import junit.framework.Test; import org.voltdb.BackendTarget; import org.voltdb.CatalogContext; import org.voltdb.SysProcSelector; import org.voltdb.VoltTable; import org.voltdb.VoltType; import org.voltdb.benchmark.tpcc.TPCCConstants; import org.voltdb.benchmark.tpcc.TPCCProjectBuilder; import org.voltdb.benchmark.tpcc.procedures.neworder; import org.voltdb.catalog.Index; import org.voltdb.catalog.Table; import org.voltdb.client.Client; import org.voltdb.client.ClientResponse; import org.voltdb.utils.VoltTableUtil; import edu.brown.hstore.Hstoreservice.Status; import edu.brown.utils.StringUtil; /** * Simple test suite for internal stats * @author pavlo */ public class TestStatsSuite extends RegressionSuite { private static final String PREFIX = "stats"; private static final double SCALEFACTOR = 0.001; /** * Constructor needed for JUnit. Should just pass on parameters to superclass. * @param name The name of the method to test. This is just passed to the superclass. */ public TestStatsSuite(String name) { super(name); } private void checkTupleAccessCount(String tableName, VoltTable result, int expected) { int total = 0; //System.err.println(VoltTableUtil.format(result)); while (result.advanceRow()) { if (result.getString("TABLE_NAME").equalsIgnoreCase(tableName)) { total += result.getLong("TUPLE_ACCESSES"); } } // WHILE assertEquals(expected, total); } private void checkIndexEntryCount(Client client, CatalogContext catalogContext, boolean checkMemory) throws Exception { // Get the row count per table per partition // We need this so we can check that the indexes have the proper number of entries Map<String, Map<Integer, Long>> rowCounts = RegressionSuiteUtil.getRowCountPerPartition(client); assertEquals(rowCounts.toString(), catalogContext.getDataTables().size(), rowCounts.size()); // System.err.println(StringUtil.formatMaps(rowCounts)); // Loop through each table and make sure that each index reports back at least // some amount of data. ClientResponse cresponse = RegressionSuiteUtil.getStats(client, SysProcSelector.INDEX); assertNotNull(cresponse); assertEquals(Status.OK, cresponse.getStatus()); VoltTable result = cresponse.getResults()[0]; for (Table tbl : catalogContext.getDataTables()) { if (tbl.getIndexes().isEmpty()) continue; Map<Integer, Long> expected = rowCounts.get(tbl.getName()); assertNotNull(tbl.toString(), expected); for (Index idx : tbl.getIndexes()) { result.resetRowPosition(); boolean found = false; while (result.advanceRow()) { String idxName = result.getString("INDEX_NAME"); String tblName = result.getString("TABLE_NAME"); String idxType = result.getString("INDEX_TYPE"); int partitionId = (int)result.getLong("PARTITION_ID"); long entryCount= result.getLong("ENTRY_COUNT"); if (tbl.getName().equalsIgnoreCase(tblName) && idx.getName().equalsIgnoreCase(idxName)) { long memoryEstimate = result.getLong("MEMORY_ESTIMATE"); //System.err.println(tblName + "------" + entryCount + "-------" + idxName + "------" + idxType + "---------" + memoryEstimate); if (checkMemory) { assert(memoryEstimate > 0) : String.format("Unexpected zero memory estimate for index %s.%s", tblName, idxName); } found = true; // Check whether the entry count is correct if it's a unique index if (idx.getUnique()) { Long expectedCnt = expected.get(partitionId); assertNotNull(String.format("TABLE:%s PARTITION:%d", tbl.getName(), partitionId), expectedCnt); assertEquals(idx.fullName(), expectedCnt.longValue(), entryCount); } } } // WHILE // Make sure that we got all the indexes for the table. assert(found) : "Did not get index stats for " + idx.fullName(); } // FOR } // FOR } /** * testTupleAccessCountIndex */ public void testTupleAccessCountIndex() throws Exception { CatalogContext catalogContext = this.getCatalogContext(); Client client = this.getClient(); RegressionSuiteUtil.initializeTPCCDatabase(catalogContext, client); ClientResponse cresponse = RegressionSuiteUtil.getStats(client, SysProcSelector.TABLE); assertNotNull(cresponse); assertEquals(Status.OK, cresponse.getStatus()); this.checkTupleAccessCount(TPCCConstants.TABLENAME_ITEM, cresponse.getResults()[0], 0); int expected = 20; for (int i = 0; i < expected; i++) { cresponse = client.callProcedure("GetItemIndex", 1); assertNotNull(cresponse); assertEquals(Status.OK, cresponse.getStatus()); } // FOR cresponse = RegressionSuiteUtil.getStats(client, SysProcSelector.TABLE); assertNotNull(cresponse); assertEquals(Status.OK, cresponse.getStatus()); this.checkTupleAccessCount(TPCCConstants.TABLENAME_ITEM, cresponse.getResults()[0], expected); } /** * testTupleAccessCountNoIndex */ public void testTupleAccessCountNoIndex() throws Exception { CatalogContext catalogContext = this.getCatalogContext(); Client client = this.getClient(); RegressionSuiteUtil.initializeTPCCDatabase(catalogContext, client); ClientResponse cresponse = RegressionSuiteUtil.getStats(client, SysProcSelector.TABLE); assertNotNull(cresponse); assertEquals(Status.OK, cresponse.getStatus()); this.checkTupleAccessCount(TPCCConstants.TABLENAME_ITEM, cresponse.getResults()[0], 0); int expected = 20; for (int i = 0; i < expected; i++) { cresponse = client.callProcedure("GetItemNoIndex"); assertNotNull(cresponse); assertEquals(Status.OK, cresponse.getStatus()); } // FOR cresponse = RegressionSuiteUtil.getStats(client, SysProcSelector.TABLE); assertNotNull(cresponse); assertEquals(Status.OK, cresponse.getStatus()); this.checkTupleAccessCount(TPCCConstants.TABLENAME_ITEM, cresponse.getResults()[0], expected); } /** * testRemoteQueryProfiling */ public void testRemoteQueryProfiling() throws Exception { CatalogContext catalogContext = this.getCatalogContext(); // Skip this test if there is only one partition if (catalogContext.numberOfPartitions == 1) return; Client client = this.getClient(); RegressionSuiteUtil.initializeTPCCDatabase(catalogContext, client); ClientResponse cresponse; // Invoke a NewOrder DTXN Object params[] = RegressionSuiteUtil.generateNewOrder(catalogContext.numberOfPartitions, true, 1, 1); cresponse = client.callProcedure(neworder.class.getSimpleName(), params); assertEquals(Status.OK, cresponse.getStatus()); assertFalse(cresponse.toString(), cresponse.isSinglePartition()); // Check that our remote query counters got increased cresponse = RegressionSuiteUtil.getStats(client, SysProcSelector.TXNPROFILER); assertNotNull(cresponse); assertEquals(Status.OK, cresponse.getStatus()); VoltTable results[] = cresponse.getResults(); assertEquals(1, results.length); // System.out.println(VoltTableUtil.format(results[0])); Map<String, Long> profilerStats = new TreeMap<String, Long>(); while (results[0].advanceRow()) { String procName = results[0].getString("PROCEDURE"); if (procName.equalsIgnoreCase(neworder.class.getSimpleName()) == false) continue; for (int i = 0, cnt = results[0].getColumnCount(); i < cnt; i++) { String colName = results[0].getColumnName(i); if (colName.toUpperCase().startsWith("FIRST_") && results[0].getColumnType(i) == VoltType.BIGINT) { profilerStats.put(colName, results[0].getLong(i)); } } // FOR } // WHILE for (String key : profilerStats.keySet()) { // All count columns should be exactly one if (key.endsWith("_CNT")) { assertEquals(key, 1l, (long)profilerStats.get(key)); } // Everything else just needs to be greater than zero else { assertTrue(key, (long)profilerStats.get(key) > 0); } } // FOR System.out.println(StringUtil.formatMaps(profilerStats)); } /** * testIndexStats */ public void testIndexStats() throws Exception { CatalogContext catalogContext = this.getCatalogContext(); Client client = this.getClient(); RegressionSuiteUtil.initializeTPCCDatabase(catalogContext, client); this.checkIndexEntryCount(client, catalogContext, true); } /** * testIndexStatsAfterDelete */ public void testIndexStatsAfterDelete() throws Exception { CatalogContext catalogContext = this.getCatalogContext(); Client client = this.getClient(); RegressionSuiteUtil.initializeTPCCDatabase(catalogContext, client); // Delete all of the tuples from each table and check to make sure the index stats are still valid for (Table tbl : catalogContext.getDataTables()) { // long numRows = RegressionSuiteUtil.getRowCount(client, tbl); String sql = String.format("DELETE FROM %s", tbl.getName()); RegressionSuiteUtil.sql(client, sql); } // FOR // Then insert one tuple back for each table for (Table tbl : catalogContext.getDataTables()) { RegressionSuiteUtil.loadRandomData(client, tbl, getRandom(), 1); assertEquals(tbl.getName(), 1, RegressionSuiteUtil.getRowCount(client, tbl)); } // FOR // Then check to make sure the counts are correct // FIXME: We should really be check memory usage here but we're not!! this.checkIndexEntryCount(client, catalogContext, false); } public static Test suite() { // the suite made here will all be using the tests from this class MultiConfigSuiteBuilder builder = new MultiConfigSuiteBuilder(TestStatsSuite.class); builder.setGlobalConfParameter("client.scalefactor", SCALEFACTOR); builder.setGlobalConfParameter("site.specexec_enable", true); builder.setGlobalConfParameter("site.specexec_nonblocking", true); builder.setGlobalConfParameter("site.txn_profiling", true); // build up a project builder for the TPC-C app TPCCProjectBuilder project = new TPCCProjectBuilder(); project.addAllDefaults(); project.addStmtProcedure("GetItemIndex", "SELECT * FROM " + TPCCConstants.TABLENAME_ITEM + " WHERE I_ID = ?"); project.addStmtProcedure("GetItemNoIndex", "SELECT * FROM " + TPCCConstants.TABLENAME_ITEM + " LIMIT 1"); VoltServerConfig config; boolean success; ///////////////////////////////////////////////////////////// // CONFIG #1: 1 Local Site/Partition running on JNI backend ///////////////////////////////////////////////////////////// config = new LocalSingleProcessServer(PREFIX + "-1part.jar", 1, BackendTarget.NATIVE_EE_JNI); success = config.compile(project); assert(success); builder.addServerConfig(config); ///////////////////////////////////////////////////////////// // CONFIG #2: 1 Local Site with 2 Partitions running on JNI backend ///////////////////////////////////////////////////////////// config = new LocalSingleProcessServer(PREFIX + "-2part.jar", 2, BackendTarget.NATIVE_EE_JNI); success = config.compile(project); assert(success); builder.addServerConfig(config); //////////////////////////////////////////////////////////// // CONFIG #3: cluster of 2 nodes running 2 site each, one replica //////////////////////////////////////////////////////////// config = new LocalCluster(PREFIX + "-cluster.jar", 2, 2, 1, BackendTarget.NATIVE_EE_JNI); success = config.compile(project); assert(success); builder.addServerConfig(config); return builder; } }