/* 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 com;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import org.voltdb.VoltTable;
import org.voltdb.VoltTableRow;
import org.voltdb.client.Client;
import org.voltdb.client.ClientConfig;
import org.voltdb.client.ClientFactory;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.NoConnectionsException;
import org.voltdb.client.ProcCallException;
import org.voltdb.client.ProcedureCallback;
import org.voltdb.client.exampleutils.AppHelper;
import org.voltdb.utils.SnapshotVerifier;
import com.deletes.Insert;
public class DeletesClient
{
final static int NUM_NAMES = 50;
static int m_averageBatchSize = 25000;
static int m_batchesToKeep = 12;
static int m_deceasedCleanupFreq = -1;
static int m_snapshotFreq = -1;
static int m_maxBatchFreq = 10;
static boolean m_blockingSnapshots = false;
static boolean m_smallStrings = false;
static String m_snapshotId = "Deletes";
static String m_snapshotDir = "/tmp/deletes";
static String[] m_names = new String[NUM_NAMES];
static Random m_rand = new Random(System.currentTimeMillis());
static long m_batchNumber = 1000;
static int m_totalRows;
static long m_highAllocMem = 0;
static long m_highAllocMemTime = 0;
static long m_highUsedMem = 0;
static long m_highUsedMemTime = 0;
static long m_highRss = 0;
static long m_highRssTime = 0;
static long m_totalInserts = 0;
static long m_totalInsertedRows = 0;
static long m_totalInsertTime = 0;
static long m_expectedInserts = 0;
static long m_totalDeletes = 0;
static long m_totalDeletedRows = 0;
static long m_totalDeleteTime = 0;
static long m_expectedDeletes = 0;
static long m_totalDeadDeletes = 0;
static long m_totalDeadDeleteTime = 0;
static long m_expectedDeadDeletes = 0;
static long m_expectedCounts = 0;
static ArrayList<Integer> m_snapshotSizes = new ArrayList<Integer>();
static boolean m_snapshotInProgress = false;
static String randomString(int maxStringSize)
{
final String lazyletters = "abcdefghijklmnopqrstuvwxyz";
StringBuilder sb = new StringBuilder();
int stringSize = m_rand.nextInt(maxStringSize) + 1;
for (int j = 0; j < stringSize; j++)
{
int index = m_rand.nextInt(lazyletters.length());
sb.append(lazyletters.charAt(index));
}
return sb.toString();
}
static void generateNames(int nameSize)
{
for (int i = 0; i < NUM_NAMES; i++)
{
m_names[i] = randomString(nameSize);
}
}
static void insertNewRecord(Client client, long batchNumber)
{
int name_idx = m_rand.nextInt(NUM_NAMES);
long age = m_rand.nextInt(100);
long weight = m_rand.nextInt(200);
long ts = batchNumber;
String desc1 = null;
String desc2 = null;
String addr1 = null;
String addr2 = null;
String addr3 = null;
String text1 = null;
String text2 = null;
String sig = null;
String company = null;
String co_addr = null;
if (m_smallStrings)
{
desc1 = randomString(60);
desc2 = randomString(60);
addr1 = randomString(30);
addr2 = randomString(60);
addr3 = randomString(60);
text1 = randomString(60);
text2 = randomString(32);
sig = randomString(16);
company = randomString(60);
co_addr = randomString(60);
}
else
{
desc1 = randomString(250);
desc2 = randomString(250);
addr1 = randomString(30);
addr2 = randomString(120);
addr3 = randomString(60);
text1 = randomString(120);
text2 = randomString(32);
sig = randomString(16);
company = randomString(60);
co_addr = randomString(250);
}
byte deceased = (byte) (m_rand.nextBoolean() ? 1 : 0);
try
{
while (!client.callProcedure(new ProcedureCallback()
{
@Override
public void clientCallback(ClientResponse response) {
if (response.getStatus() != ClientResponse.SUCCESS){
System.out.println("failed insert");
System.out.println(response.getStatusString());
}
m_expectedInserts--; //we don't care if tx fail, but don't get stuck in yield
}
},
Insert.class.getSimpleName(),
m_names[name_idx],
age,
weight,
desc1,
desc2,
addr1,
addr2,
addr3,
text1,
text2,
sig,
ts,
company,
co_addr,
deceased)
)
{
try
{
client.backpressureBarrier();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (NoConnectionsException e) {
System.err.println("Lost connection to database, terminating");
System.exit(-1);
} catch (IOException e) {
e.printStackTrace();
}
}
static void insertBatch(Client client, boolean max_batch)
{
Date date = new Date();
System.out.println(date.toString() + "\n\tTotal rows currently: " + m_totalRows);
int to_insert = m_rand.nextInt(m_averageBatchSize * 2) + 1;
if (max_batch)
{
to_insert = m_averageBatchSize * 2;
}
System.out.println("\tInserting: " + to_insert + " rows");
m_expectedInserts = to_insert;
long start_time = System.currentTimeMillis();
for (int j = 0; j < to_insert; j++)
{
insertNewRecord(client, m_batchNumber);
}
m_batchNumber++;
m_totalRows += to_insert;
while (m_expectedInserts != 0)
{
Thread.yield();
}
long elapsed = System.currentTimeMillis() - start_time;
m_totalInserts += to_insert;
m_totalInsertTime += elapsed;
System.out.println("\tBatch: " + to_insert + " took " +
elapsed + " millis");
System.out.println("\t\t (" + ((to_insert * 1000)/elapsed) + " tps)");
System.out.println("\tTotal insert TPS: " + (m_totalInserts * 1000)/m_totalInsertTime);
}
static void parseStats(ClientResponse resp)
{
// Go ghetto for now and assume we're running on one host.
VoltTable memory_stats = resp.getResults()[0];
//System.out.println("mem stats: " + memory_stats);
long rss = memory_stats.fetchRow(0).getLong("RSS");
if (rss > m_highRss)
{
m_highRss = rss;
m_highRssTime = System.currentTimeMillis();
}
long alloc_mem = 0;
long used_mem = 0;
alloc_mem += memory_stats.fetchRow(0).getLong("JAVAUSED");
alloc_mem += memory_stats.fetchRow(0).getLong("TUPLEALLOCATED");
alloc_mem += memory_stats.fetchRow(0).getLong("INDEXMEMORY");
alloc_mem += memory_stats.fetchRow(0).getLong("POOLEDMEMORY");
used_mem += memory_stats.fetchRow(0).getLong("JAVAUSED");
used_mem += memory_stats.fetchRow(0).getLong("TUPLEDATA");
used_mem += memory_stats.fetchRow(0).getLong("INDEXMEMORY");
used_mem += memory_stats.fetchRow(0).getLong("STRINGMEMORY");
if (alloc_mem > m_highAllocMem)
{
m_highAllocMem = alloc_mem;
m_highAllocMemTime = System.currentTimeMillis();
}
if (used_mem > m_highUsedMem)
{
m_highUsedMem = used_mem;
m_highUsedMemTime = System.currentTimeMillis();
}
System.out.println("CURRENT MEMORY TOTALS (USED, ALLOCATED, RSS):");
System.out.println("CURRENT," + used_mem * 1000 + "," + alloc_mem * 1000 + "," + rss * 1000);
Date blah = new Date(m_highUsedMemTime);
System.out.println("LARGEST MEMORY USED: " + m_highUsedMem * 1000 + " at " + blah.toString());
blah = new Date(m_highAllocMemTime);
System.out.println("LARGEST MEMORY ALLOCATED: " + m_highAllocMem * 1000 + " at " + blah.toString());
blah = new Date(m_highRssTime);
System.out.println("LARGEST RSS: " + m_highRss * 1000 + " at " + blah.toString());
}
static void collectStats(Client client)
{
try {
ClientResponse resp = client.callProcedure("@Statistics", "management", 0);
parseStats(resp);
} catch (NoConnectionsException e) {
System.err.println("Lost connection to database, terminating");
System.exit(-1);
} catch (IOException e) {
e.printStackTrace();
}
catch (ProcCallException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
static void deleteBatch(Client client, long batchesToKeep)
{
long prune_ts = m_batchNumber - batchesToKeep;
Date date = new Date();
System.out.println(date.toString() + "\n\tPruning batches older than batch: " + prune_ts);
m_expectedDeletes = NUM_NAMES;
long start_time = System.currentTimeMillis();
for (int i = 0; i < NUM_NAMES; i++)
{
try
{
while (!client.callProcedure(new ProcedureCallback()
{
@Override
public void clientCallback(ClientResponse response) {
if (response.getStatus() != ClientResponse.SUCCESS){
System.out.println("failed delete batch");
System.out.println(response.getStatusString());
}
else
{
m_totalRows -= response.getResults()[0].asScalarLong();
m_totalDeletedRows += response.getResults()[0].asScalarLong();
}
m_expectedDeletes--; //we don't care if tx fail, but don't get stuck in yield
}
},
"DeleteOldBatches", m_names[i], prune_ts)
)
{
try
{
client.backpressureBarrier();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
catch (NoConnectionsException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
while (m_expectedDeletes != 0)
{
Thread.yield();
}
long elapsed = System.currentTimeMillis() - start_time;
m_totalDeletes += NUM_NAMES;
m_totalDeleteTime += elapsed;
System.out.println("\tAfter delete, total rows: " + m_totalRows);
System.out.println("\tDeleting batch: " + NUM_NAMES + " took " +
elapsed + " millis");
System.out.println("\t\t (" + ((NUM_NAMES * 1000)/elapsed) + " tps)");
System.out.println("\tTotal delete TPS: " + (m_totalDeletes * 1000)/m_totalDeleteTime);
System.out.println("\tTotal delete RPS: " + (m_totalDeletedRows * 1000)/m_totalDeleteTime);
}
static void deleteDeceased(Client client)
{
Date date = new Date();
System.out.println(date.toString() + "\n\tDeleting deceased records...");
m_expectedDeadDeletes = NUM_NAMES;
long start_time = System.currentTimeMillis();
for (int i = 0; i < NUM_NAMES; i++)
{
try
{
while (!client.callProcedure(new ProcedureCallback()
{
@Override
public void clientCallback(ClientResponse response) {
if (response.getStatus() != ClientResponse.SUCCESS){
System.out.println("failed delete deceased");
System.out.println(response.getStatusString());
}
else
{
m_totalRows -= response.getResults()[0].asScalarLong();
}
m_expectedDeadDeletes--; //we don't care if tx fail, but don't get stuck in yield
}
},
"DeleteDeceased", m_names[i])
)
{
try {
client.backpressureBarrier();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
catch (NoConnectionsException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
while (m_expectedDeadDeletes != 0)
{
Thread.yield();
}
long elapsed = System.currentTimeMillis() - start_time;
m_totalDeadDeletes += NUM_NAMES;
m_totalDeadDeleteTime += elapsed;
System.out.println("\tAfter dead deletes, total rows: " + m_totalRows);
System.out.println("\tDeleting deceased: " + NUM_NAMES + " took " +
elapsed + " millis");
System.out.println("\t\t (" + ((NUM_NAMES * 1000)/elapsed) + " tps)");
System.out.println("\tTotal delete TPS: " + (m_totalDeadDeletes * 1000)/m_totalDeadDeleteTime);
}
static void countBatch(Client client, long batch)
{
Date date = new Date();
System.out.println(date.toString() + "\n\tCounting batch: " + batch);
m_expectedCounts = 1;
long start_time = System.currentTimeMillis();
for (int i = 0; i < 1; i++)
{
try
{
while (!client.callProcedure(new ProcedureCallback()
{
@Override
public void clientCallback(ClientResponse response) {
if (response.getStatus() != ClientResponse.SUCCESS){
System.out.println("failed count batch");
System.out.println(response.getStatusString());
}
else
{
System.out.println("\tBatch has " +
response.getResults()[0].asScalarLong() +
" items");
}
m_expectedCounts--; //we don't care if tx fail, but don't get stuck in yield
}
},
"CountBatchSize", "", batch)
)
{
try
{
client.backpressureBarrier();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
catch (NoConnectionsException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
while (m_expectedCounts != 0)
{
Thread.yield();
}
long elapsed = System.currentTimeMillis() - start_time;
}
// stolen from TestSaveRestoreSysproc
static void validateSnapshot()
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
PrintStream original = System.out;
try {
System.setOut(ps);
String args[] = new String[] {
m_snapshotId,
"--dir",
m_snapshotDir
};
SnapshotVerifier.main(args);
ps.flush();
String reportString = baos.toString("UTF-8");
if (reportString.startsWith("Snapshot corrupted"))
{
System.err.println(reportString);
System.exit(-1);
}
} catch (UnsupportedEncodingException e) {}
finally {
System.setOut(original);
}
}
public static void checkSnapshotComplete(Client client)
{
// Check for outstanding snapshot
VoltTable[] results = null;
try
{
results = client.callProcedure("@SnapshotStatus").getResults();
//System.out.println(results[0]);
}
catch (NoConnectionsException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
catch (IOException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
catch (ProcCallException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
m_snapshotInProgress = false;
while (results[0].advanceRow())
{
Long end_time = results[0].getLong("END_TIME");
if (end_time == 0)
{
m_snapshotInProgress = true;
return;
}
}
if (results[0].getRowCount() > 0)
{
validateSnapshot();
}
}
public static void performSnapshot(Client client)
{
checkSnapshotComplete(client);
if (m_snapshotInProgress)
{
System.out.println("Snapshot still in progress, bailing");
return;
}
try
{
VoltTable[] results = client.callProcedure("@SnapshotDelete", new String[] {m_snapshotDir},
new String[] {m_snapshotId}).getResults();
}
catch (NoConnectionsException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
catch (IOException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
catch (ProcCallException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
// m_totalRows should be accurate at this point
m_snapshotSizes.add(m_totalRows);
System.out.println("Performing Snapshot with total rows: " + m_totalRows);
try
{
if (m_blockingSnapshots) {
ClientResponse response = client.callProcedure("@SnapshotSave", m_snapshotDir, m_snapshotId, 1);
if (response.getStatus() != ClientResponse.SUCCESS)
{
System.out.println("failed snapshot");
System.out.println(response.getStatusString());
}
}
else {
client.callProcedure(
new ProcedureCallback()
{
@Override
public void clientCallback(ClientResponse response) {
if (response.getStatus() != ClientResponse.SUCCESS)
{
System.out.println("failed snapshot");
System.out.println(response.getStatusString());
}
}
},
"@SnapshotSave", m_snapshotDir, m_snapshotId, 0);
}
}
catch (NoConnectionsException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (ProcCallException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args)
{
// Use the AppHelper utility class to retrieve command line application parameters
// Define parameters and pull from command line
AppHelper apph = new AppHelper(DeletesClient.class.getCanonicalName())
.add("duration", "run_duration_in_seconds", "Benchmark duration, in seconds.", 240)
.add("average-batch-size", "average_batch_size", "Average batch size", 150000)
.add("batches", "num_batches_to_keep", "Number of batches to keep", 5)
.add("cleanup-freq", "cleanup_frequency", "Cleanup frequency, in seconds.", 6)
.add("snapshot-freq", "cycles_between_snapshots", "Snapshot frequency, in seconds. -1 to turn off snapshots", -1)
.add("block-snapshots", "use_blocking_snapshots_snapshots", "Blocking snapshots (true|false)", "false")
.add("small-strings", "use_inline_strings", "Forces all the strings to be inlined strings (true|false)", "false")
.add("servers", "comma_separated_server_list", "List of VoltDB servers to connect to.", "localhost")
.setArguments(args)
;
m_averageBatchSize = apph.intValue("average-batch-size");
m_batchesToKeep = apph.intValue("batches");
m_deceasedCleanupFreq = apph.intValue("cleanup-freq");
m_snapshotFreq = apph.intValue("snapshot-freq");
m_blockingSnapshots = apph.booleanValue("block-snapshots");
m_smallStrings = apph.booleanValue("small-strings");
long duration = apph.longValue("duration");
String commaSeparatedServers = apph.stringValue("servers");
apph.validate("average-batch-size", (m_averageBatchSize > 0));
apph.validate("batches", (m_batchesToKeep >= 0));
apph.validate("duration", (duration >= 0));
apph.validate("cleanup-freq", (m_deceasedCleanupFreq > 0));
apph.printActualUsage();
System.out.println("Starting Deletes app with:");
System.out.printf("\tAverage batch size of %d\n", m_averageBatchSize);
System.out.printf("\tKeeping %d batches\n", m_batchesToKeep);
System.out.printf("\tCleaning up deceased every %d batches\n", m_deceasedCleanupFreq);
System.out.printf("\tSnapshotting every %d batches\n", m_snapshotFreq);
// parse the server list
List<String> servers = new LinkedList<String>();
String[] commaSeparatedServersParts = commaSeparatedServers.split(",");
for (String server : commaSeparatedServersParts) {
servers.add(server.trim());
}
File tmpdir = new File(m_snapshotDir);
tmpdir.mkdir();
generateNames(16);
Client client = null;
ClientConfig config = new ClientConfig("program", "none");
config.setProcedureCallTimeout(Long.MAX_VALUE);
client = ClientFactory.createClient(config);
for (String server : servers) {
try {
client.createConnection(server);
} catch (UnknownHostException e) {
e.printStackTrace();
System.exit(-1);
} catch (IOException e) {
System.err.println("Could not connect to database, terminating: (" + server + ")");
System.exit(-1);
}
}
// Start with the maximum data set we could possibly fill
for (int i = 0; i < m_batchesToKeep; i++)
{
insertBatch(client, true);
}
// now add a batch and remove a batch
long deceased_counter = 0;
long snapshot_counter = 0;
long max_batch_counter = 0;
boolean fill_max = false;
long max_batch_remaining = 0;
final long endTime = System.currentTimeMillis() + (1000l * duration);
final long startTime = System.currentTimeMillis();
while (endTime > System.currentTimeMillis())
{
// if (max_batch_counter == m_maxBatchFreq)
// {
// fill_max = true;
// max_batch_remaining = m_batchesToKeep;
// max_batch_counter = 0;
// }
// else if (fill_max)
// {
// max_batch_remaining--;
// fill_max = true;
// if (max_batch_remaining == 0)
// {
// fill_max = false;
// }
// }
// else
// {
// max_batch_counter++;
// fill_max = false;
// }
insertBatch(client, fill_max);
collectStats(client);
snapshot_counter++;
if (snapshot_counter == m_snapshotFreq)
{
performSnapshot(client);
snapshot_counter = 0;
}
deceased_counter++;
if (deceased_counter == m_deceasedCleanupFreq)
{
deleteDeceased(client);
deceased_counter = 0;
}
countBatch(client, m_batchNumber - m_batchesToKeep - 1);
deleteBatch(client, m_batchesToKeep);
}
System.out.printf("\tTotal runtime: %d seconds\n", (System.currentTimeMillis() - startTime) / 1000l);
}
}