package org.voltdb.regressionsuites; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import junit.framework.Test; import org.voltdb.BackendTarget; import org.voltdb.CatalogContext; import org.voltdb.SysProcSelector; import org.voltdb.VoltSystemProcedure; import org.voltdb.VoltTable; import org.voltdb.client.Client; import org.voltdb.client.ClientResponse; import org.voltdb.sysprocs.AdHoc; import org.voltdb.sysprocs.EvictHistory; import org.voltdb.sysprocs.EvictTuples; import org.voltdb.sysprocs.EvictedAccessHistory; import org.voltdb.sysprocs.Statistics; import org.voltdb.utils.VoltTableUtil; import edu.brown.HStoreSiteTestUtil.LatchableProcedureCallback; import edu.brown.benchmark.voter.VoterConstants; import edu.brown.benchmark.voter.VoterProjectBuilder; import edu.brown.benchmark.voter.procedures.Initialize; import edu.brown.benchmark.voter.procedures.Vote; import edu.brown.hstore.Hstoreservice.Status; import edu.brown.profilers.AntiCacheManagerProfiler; import edu.brown.utils.StringUtil; /** * Anti-Caching Test Suite * @author pavlo */ public class TestAntiCacheSuite extends RegressionSuite { private static final String PREFIX = "anticache"; private static final int NOTIFY_TIMEOUT = 2000; // ms private static final int NUM_VOTES = 100; private int readBackTracker; /** * 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 TestAntiCacheSuite(String name) { super(name); } // -------------------------------------------------------------------------------------------- // UTILITY METHODS // -------------------------------------------------------------------------------------------- private void initializeDatabase(Client client) throws Exception { // System.err.println("Initializing ..."); Object params[] = { VoterConstants.NUM_CONTESTANTS, VoterConstants.CONTESTANT_NAMES_CSV }; ClientResponse cresponse = client.callProcedure(Initialize.class.getSimpleName(), params); assertNotNull(cresponse); assertEquals(Status.OK, cresponse.getStatus()); // System.err.println("done."); } private void loadVotes(Client client, int num_txns) throws Exception { // System.err.println("Loading data..."); LatchableProcedureCallback callback = new LatchableProcedureCallback(num_txns); for (int i = 0; i < num_txns; i++) { Object params[] = { new Long(i), TestVoterSuite.phoneNumber+i, TestVoterSuite.contestantNumber, num_txns+1 }; client.callProcedure(callback, Vote.class.getSimpleName(), params); } // FOR // Wait until they all finish boolean result = callback.latch.await(NOTIFY_TIMEOUT, TimeUnit.MILLISECONDS); assertTrue(callback.toString(), result); for (ClientResponse cr : callback.responses) { assertEquals(cr.toString(), Status.OK, cr.getStatus()); } // Make sure that our vote is actually in the real table and materialized views String query = "SELECT COUNT(*) FROM votes"; String procName = VoltSystemProcedure.procCallName(AdHoc.class); ClientResponse cresponse = client.callProcedure(procName, query); assertEquals(Status.OK, cresponse.getStatus()); VoltTable results[] = cresponse.getResults(); assertEquals(1, results.length); assertEquals(num_txns, results[0].asScalarLong()); // System.err.println("Finished Loading Data."); } private Map<Integer, VoltTable> evictData(Client client) throws Exception { // System.err.printf("Evicting data..."); String procName = VoltSystemProcedure.procCallName(EvictTuples.class); CatalogContext catalogContext = this.getCatalogContext(); String tableNames[] = { VoterConstants.TABLENAME_VOTES }; LatchableProcedureCallback callback = new LatchableProcedureCallback(catalogContext.numberOfPartitions); // long evictBytes[] = { Integer.MAX_VALUE }; long evictBytes[] = {10000}; int numBlocks[] = { 1 }; for (int partition : catalogContext.getAllPartitionIds()) { // System.err.printf("Evicting data at partition %d...\n", partition); String children[] = null; Object params[] = { partition, tableNames, children, evictBytes, numBlocks }; boolean result = client.callProcedure(callback, procName, params); assertTrue(result); } // FOR // Wait until they all finish boolean result = callback.latch.await(NOTIFY_TIMEOUT, TimeUnit.MILLISECONDS); assertTrue(callback.toString(), result); // Construct a mapping BasePartition->VoltTable Map<Integer, VoltTable> m = new TreeMap<Integer, VoltTable>(); for (ClientResponse cr : callback.responses) { assertEquals(cr.toString(), Status.OK, cr.getStatus()); assertEquals(cr.toString(), 1, cr.getResults().length); m.put(cr.getBasePartition(), cr.getResults()[0]); } // FOR assertEquals(catalogContext.numberOfPartitions, m.size()); // System.err.printf("Finished evicting data."); return (m); } private void checkEvictedAccess(String procName, Object params[], int expected) throws Exception { Client client = this.getClient(); this.initializeDatabase(client); this.loadVotes(client, NUM_VOTES); int num_evicts = 5; for (int i = 0; i < num_evicts; i++) { this.evictData(client); } // FOR // Now force the system to fetch the block back in // long expected = 1; ClientResponse cresponse = client.callProcedure(procName, params); assertEquals(cresponse.toString(), Status.OK, cresponse.getStatus()); assertEquals(cresponse.toString(), 1, cresponse.getResults().length); VoltTable result = cresponse.getResults()[0]; // result.advanceRow(); // assertEquals(cresponse.toString(), expected, result.getRowCount()); // Our stats should now come back with one evicted access cresponse = client.callProcedure(VoltSystemProcedure.procCallName(EvictedAccessHistory.class)); assertEquals(cresponse.toString(), Status.OK, cresponse.getStatus()); assertEquals(cresponse.toString(), 1, cresponse.getResults().length); result = cresponse.getResults()[0]; // assertEquals(1, result.getRowCount()); System.err.println(VoltTableUtil.format(result)); while (result.advanceRow()) { assertEquals(procName, result.getString("PROCEDURE")); } // WHILE } // -------------------------------------------------------------------------------------------- // TEST CASES // -------------------------------------------------------------------------------------------- /** * testEvictEmptyTable */ public void testEvictEmptyTable() throws Exception { Client client = this.getClient(); this.initializeDatabase(client); // Force an eviction on a table before putting anything in it Map<Integer, VoltTable> evictResults = this.evictData(client); System.err.println(StringUtil.formatMaps(evictResults)); System.err.println("-------------------------------"); } /** * testProfiling */ public void testProfiling() throws Exception { Client client = this.getClient(); this.initializeDatabase(client); this.loadVotes(client, 100); // Force an eviction Map<Integer, VoltTable> evictResults = this.evictData(client); for (int partition : evictResults.keySet()) { System.err.println("Partition " + partition); System.err.println(StringUtil.prefix(" ", VoltTableUtil.format(evictResults.get(partition)))); } System.err.println("-------------------------------"); // Our stats should now come back with one eviction executed String procName = VoltSystemProcedure.procCallName(Statistics.class); Object params[] = { SysProcSelector.ANTICACHE.name(), 0 }; ClientResponse cresponse = client.callProcedure(procName, params); assertEquals(cresponse.toString(), Status.OK, cresponse.getStatus()); assertEquals(cresponse.toString(), 1, cresponse.getResults().length); VoltTable statsResult = cresponse.getResults()[0]; System.err.println(VoltTableUtil.format(statsResult)); // We need this just to get the name of the column AntiCacheManagerProfiler profiler = new AntiCacheManagerProfiler(); String colName = profiler.eviction_time.getName().toUpperCase()+"_CNT"; while (statsResult.advanceRow()) { System.err.println("colName: " + colName); int partition = (int)statsResult.getLong("PARTITION"); VoltTable vt = evictResults.get(partition); boolean adv = vt.advanceRow(); assert(adv); long expected = vt.getLong("ANTICACHE_BLOCKS_EVICTED"); assertEquals("Partition "+partition, expected, statsResult.getLong(colName)); } // WHILE } /** * testEvictHistory */ public void testEvictHistory() throws Exception { CatalogContext catalogContext = this.getCatalogContext(); Client client = this.getClient(); this.initializeDatabase(client); this.loadVotes(client, 100); int num_evicts = 5; for (int i = 0; i < num_evicts; i++) { this.evictData(client); } // FOR // Our stats should now come back with one eviction executed String procName = VoltSystemProcedure.procCallName(EvictHistory.class); ClientResponse cresponse = client.callProcedure(procName); assertEquals(cresponse.toString(), Status.OK, cresponse.getStatus()); assertEquals(cresponse.toString(), 1, cresponse.getResults().length); VoltTable result = cresponse.getResults()[0]; assertEquals(num_evicts * catalogContext.numberOfPartitions, result.getRowCount()); System.err.println(VoltTableUtil.format(result)); while (result.advanceRow()) { long start = result.getLong("START"); long stop = result.getLong("STOP"); assert(start <= stop) : start + " <= " + stop; } // WHILE } /** * testEvictedAccessSeqScan */ public void testEvictedAccessSeqScan() throws Exception { // Object params[] = { }; // FIXME this.checkEvictedAccess("GetAllVotes", params, NUM_VOTES); } /** * testEvictedAccessIndexScan */ public void testEvictedAccessIndexScan() throws Exception { Object params[] = { 1 }; this.checkEvictedAccess("GetVote", params, 1); } /** * testEvictedAccessIndexJoin */ public void testEvictedAccessIndexJoin() throws Exception { Object params[] = { 1 }; this.checkEvictedAccess("GetVoteJoin", params, 1); } public static Test suite() { VoltServerConfig config = null; // the suite made here will all be using the tests from this class MultiConfigSuiteBuilder builder = new MultiConfigSuiteBuilder(TestAntiCacheSuite.class); builder.setGlobalConfParameter("site.exec_voltdb_procinfo", true); builder.setGlobalConfParameter("site.anticache_enable", true); builder.setGlobalConfParameter("site.anticache_profiling", true); builder.setGlobalConfParameter("site.anticache_reset", true); builder.setGlobalConfParameter("site.anticache_check_interval", Integer.MAX_VALUE); builder.setGlobalConfParameter("site.anticache_batching", false); // build up a project builder for the TPC-C app VoterProjectBuilder project = new VoterProjectBuilder(); project.addAllDefaults(); project.markTableEvictable(VoterConstants.TABLENAME_VOTES); project.markTableEvictable(VoterConstants.TABLENAME_CONTESTANTS); project.addStmtProcedure("GetVote", "SELECT * FROM " + VoterConstants.TABLENAME_VOTES + " WHERE vote_id = ?"); project.addStmtProcedure("GetAllVotes", "SELECT * FROM " + VoterConstants.TABLENAME_VOTES); project.addStmtProcedure("GetVoteJoin", String.format("SELECT phone_number, contestant_number FROM %s, %s " + " WHERE vote_id = ? " + " AND %s.contestant_number = %s.contestant_number", VoterConstants.TABLENAME_VOTES, VoterConstants.TABLENAME_CONTESTANTS, VoterConstants.TABLENAME_VOTES, VoterConstants.TABLENAME_CONTESTANTS)); 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; } }