Java tutorial
/* * * Copyright (c) 2015 University of Massachusetts * * Licensed under the Apache License, Version 2.0 (the "License"); you * may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. * * Initial developer(s): Westy, Emmanuel Cecchet * */ package edu.umass.cs.gnsclient.client.testing; import edu.umass.cs.gnsclient.client.GNSClient; import edu.umass.cs.gnscommon.packets.CommandPacket; import edu.umass.cs.gnscommon.utils.Format; import edu.umass.cs.gnsclient.client.util.GuidEntry; import edu.umass.cs.gnsclient.client.util.GuidUtils; import edu.umass.cs.gnscommon.utils.ThreadUtils; import edu.umass.cs.gnscommon.exceptions.client.ClientException; import edu.umass.cs.gnscommon.utils.RandomString; import edu.umass.cs.utils.DelayProfiler; import static edu.umass.cs.gnsclient.client.CommandUtils.*; import edu.umass.cs.gnsclient.client.GNSClientCommands; import edu.umass.cs.gnscommon.CommandType; import java.net.InetSocketAddress; import java.awt.HeadlessException; import java.io.IOException; import java.util.Collections; import java.util.Date; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import edu.umass.cs.gnscommon.GNSProtocol; /** * Client side read throughput test using asynchronous send. * Can be used to verify server throughput. * Uses an asynchronous method to send reads from the client at prescribed rates. * It verifies that response packets are sent back to the client and measures * the latency and errors associated with those packets. * * It is run on the command line like this: * * <code>./scripts/client/runClient edu.umass.cs.gnsclient.client.testing.ThroughputAsynchMultiClientTest</code> Using the above incantation with an argument of -help will show the help text listing all the arguments. Some important ones are with example arguments are: -alias fred@cs.umass.edu - specifies the account alias (your email) used to created your GNS account guid -host pear.cs.umass.edu - the host where the GNS(LNS) server is running -port 24403 - the port that the server is listening on (24403 is AR, 24398 is LNS) -rate - the number of how many client requests per second to generate -inc - specifies the GNSProtocol.N.toString() increase for rate every 5 seconds (if not given rate is uniform) -clients - specifies the number of clients to use -requests - specifies the number of requests to send per batch sent by each client Here's a full example command: ./scripts/client/runClient edu.umass.cs.gnsclient.client.testing.ThroughputAsynchMultiClientTest -host 10.0.1.50 -port 24403 -rate 1000 -inc 500 -clients 10 -requests 20 This means to start at 1000 requests per second and increment the rate by 500 every 5 seconds You can also specific a fixed rate using just the -rate option Output looks like this: {pre} 21:40:50 EDT Attempted rate/s 1000..... 21:40:55 EDT Actual rate/s: 977.8994719342851 Average latency: 13.09 Outstanding packets: 0 Errors: 0 21:40:55 EDT Attempted rate/s 1500........ 21:41:00 EDT Actual rate/s: 1459.5660749506903 Average latency: 12.54 Outstanding packets: 1 Errors: 0 21:41:00 EDT Attempted rate/s 2000.......... 21:41:05 EDT Actual rate/s: 1914.257228315055 Average latency: 12.4 Outstanding packets: 1 Errors: 0 21:41:05 EDT Attempted rate/s 2500............ 21:41:10 EDT Actual rate/s: 2382.370458606313 Average latency: 12.74 Outstanding packets: 1 Errors: 0 21:41:10 EDT Attempted rate/s 3000............... 21:41:15 EDT Actual rate/s: 2853.745541022592 Average latency: 13.31 Outstanding packets: 1 Errors: 0 {\pre} Outstanding packets should be close to zero if the server is keeping up. */ public class ThroughputAsynchMultiClientTest { /** * How long to collect samples for before reporting results. */ private static final int CYCLE_TIME = 10000; private static final String DEFAULT_FIELD = "environment"; private static final String DEFAULT_VALUE = "8675309"; /** * The default number of client instances to use. */ private static final int DEFAULT_NUMBER_OF_CLIENTS = 10; /** * The default number of requests each client sends each time. */ private static final int DEFAULT_NUMBER_REQUESTS_PER_CLIENT = 10; /** * The default alias to use. Overridden by the "alias" command line arg. */ private static String accountAlias = "boo@hoo.com"; /** * The number of clients. Set the "clients" command line arg. */ private static int numberOfClients; /** * The number of guids. Set the "guids" command line arg. */ private static int numberOfGuids; /** * Stores the clients we are using. */ private static GNSClientCommands clients[]; /** * The account guid we are using. */ private static GuidEntry masterGuid; /** * Stores the guids we are updating fields in. */ private static String subGuids[]; /** * If this is true we are doing reads, otherwise doing updates. */ private static boolean doingReads = true; /** * The alias of the guid we will be updating. Null means to use the account guid. */ private static String updateAlias = null; /** * The field in the guid we will be updating. Defaults to "environment".; */ private static String updateField = null; /** * The value we will be using for updating. Defaults to "8675309".; */ private static String updateValue = null; /** * Expected max resolution of the millisecond clock. */ private static final long minSleepInterval = 10; /** * Our command packet cache. We need a separate command packet for each client for each guid. */ private static CommandPacket commmandPackets[][]; private static ExecutorService execPool; /** * Keeps track of the set of guids chosen each cycle. */ private Set<Integer> chosen; /** * Creates a ThroughputStress instance with the given arguments. * * @param alias - the alias to use to create the account guid. null uses "boo@hoo.com". */ public ThroughputAsynchMultiClientTest(String alias) { InetSocketAddress address; if (alias != null) { accountAlias = alias; } clients = new GNSClientCommands[numberOfClients]; subGuids = new String[numberOfGuids]; commmandPackets = new CommandPacket[numberOfGuids][numberOfClients]; execPool = Executors.newFixedThreadPool(numberOfClients); chosen = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>()); try { for (int i = 0; i < numberOfClients; i++) { clients[i] = new GNSClientCommands(null); } } catch (IOException e) { System.out.println("Unable to create client: " + e); e.printStackTrace(); System.exit(1); } try { masterGuid = GuidUtils.lookupOrCreateAccountGuid(clients[0], accountAlias, "password", true); } catch (Exception e) { System.out.println("Exception when we were not expecting it: " + e); e.printStackTrace(); System.exit(1); } } /** * The main routine run from the command line. * * @param args * @throws Exception */ public static void main(String args[]) throws Exception { try { CommandLine parser = initializeOptions(args); if (parser.hasOption("help")) { printUsage(); System.exit(1); } String alias = parser.getOptionValue("alias"); String host = parser.getOptionValue("host"); String port = parser.getOptionValue("port"); boolean disableSSL = parser.hasOption("disableSSL"); if (parser.hasOption("op") && ("update".equals(parser.getOptionValue("op")) || "write".equals(parser.getOptionValue("op")))) { doingReads = false; System.out.println("Testing updates"); } else { doingReads = true; System.out.println("Testing reads"); } int rate = (parser.hasOption("rate")) ? Integer.parseInt(parser.getOptionValue("rate")) : 1; int increment = (parser.hasOption("inc")) ? Integer.parseInt(parser.getOptionValue("inc")) : 10; numberOfClients = (parser.hasOption("clients")) ? Integer.parseInt(parser.getOptionValue("clients")) : DEFAULT_NUMBER_OF_CLIENTS; int requestsPerClient = (parser.hasOption("requests")) ? Integer.parseInt(parser.getOptionValue("requests")) : DEFAULT_NUMBER_REQUESTS_PER_CLIENT; numberOfGuids = (parser.hasOption("guids")) ? Integer.parseInt(parser.getOptionValue("guids")) : 1; updateAlias = parser.hasOption("updateAlias") ? parser.getOptionValue("updateAlias") : null; updateField = parser.hasOption("updateField") ? parser.getOptionValue("updateField") : DEFAULT_FIELD; updateValue = parser.hasOption("updateValue") ? parser.getOptionValue("updateValue") : DEFAULT_VALUE; ThroughputAsynchMultiClientTest test = new ThroughputAsynchMultiClientTest(alias); test.createSubGuidsAndWriteValue(parser.hasOption("useExistingGuids")); // prebuild a packet for each client for (int clientIndex = 0; clientIndex < numberOfClients; clientIndex++) { for (int i = 0; i < numberOfGuids; i++) { if (doingReads) { commmandPackets[i][clientIndex] = createReadCommandPacket(clients[clientIndex], subGuids[i], updateField, masterGuid); } else { JSONObject json = new JSONObject(); json.put(updateField, updateValue); commmandPackets[i][clientIndex] = createUpdateCommandPacket(clients[clientIndex], subGuids[i], json, masterGuid); } } } if (parser.hasOption("inc")) { test.ramp(rate, increment, requestsPerClient); } else if (parser.hasOption("rate")) { test.ramp(rate, 0, requestsPerClient); } else { // should really do this earlier printUsage(); System.exit(1); } // cleanup test.removeSubGuid(); for (int i = 0; i < numberOfClients; i++) { clients[i].close(); } System.exit(0); } catch (HeadlessException e) { System.out .println("When running headless you'll need to specify the host and port on the command line"); printUsage(); System.exit(1); } } /** * Creates the guids where do all the data access. * If the user specifies an updateAlias we make one * guid using that alias and use that for all clients. Otherwise we * make a bunch of random guids. * @param useExistingGuids */ public void createSubGuidsAndWriteValue(boolean useExistingGuids) { try { // If updateAlias is not null that means we want to create a single alias // to do all the operations from if (updateAlias != null) { // if it was specified find or create the guid for updateAlias subGuids[0] = GuidUtils.lookupOrCreateGuid(clients[0], masterGuid, updateAlias, true).getGuid(); } JSONArray existingGuids = null; GuidEntry createdGuids[] = new GuidEntry[numberOfGuids]; // If updateAlias is null that means we want to use // a bunch of aliases that are subalises to the masterGuid if (updateAlias == null) { if (!useExistingGuids) { // Create the the guids set up below. for (int i = 0; i < numberOfGuids; i++) { createdGuids[i] = clients[i].guidCreate(masterGuid, "subGuid" + RandomString.randomString(6)); System.out.println("Created: " + createdGuids[i].getEntityName()); } } try { JSONObject command = createCommand(CommandType.LookupRandomGuids, GNSProtocol.GUID.toString(), masterGuid.getGuid(), GNSProtocol.GUIDCNT.toString(), numberOfGuids); String result = clients[0] .execute(new CommandPacket((long) (Math.random() * Long.MAX_VALUE), command)) .getResultString();// checkResponse(clients[0].sendCommandAndWait(command)); if (!result.startsWith(GNSProtocol.BAD_RESPONSE.toString())) { existingGuids = new JSONArray(result); } else { System.out.println("Problem reading random guids " + result); System.exit(-1); } } catch (JSONException | IOException | ClientException e) { System.out.println("Problem reading random guids " + e); System.exit(-1); } } // Ensure that we have enough guids if (existingGuids.length() == 0) { System.out.println("No guids found in account guid " + masterGuid.getEntityName() + "; exiting."); System.exit(-1); } if (existingGuids.length() < numberOfGuids) { System.out.println(existingGuids.length() + " guids found in account guid " + masterGuid.getEntityName() + " which is not enough" + "; exiting."); System.exit(-1); } System.out.println("Using " + numberOfGuids + " guids"); for (int i = 0; i < numberOfGuids; i++) { if (updateAlias != null) { // if it was specified copy the single one subGuids[i] = subGuids[0]; } else { // otherwise use the ones we discovered or created above subGuids[i] = existingGuids.getString(i); //subGuids[i] = clients[i].guidCreate(masterGuid, "subGuid" + Utils.randomString(6)).getGuid(); //System.out.println("Using: " + subGuids[i]); } } } catch (Exception e) { System.out.println("Exception creating the subguid: " + e); e.printStackTrace(); System.exit(1); } if (doingReads) { try { // if the user specified one guid we just need to update that one if (updateAlias != null) { clients[0].fieldUpdate(subGuids[0], updateField, updateValue, masterGuid); } else { // otherwise write the value into all guids System.out.println("Initializing fields."); for (int i = 0; i < numberOfGuids; i++) { clients[i].fieldUpdate(subGuids[i], updateField, updateValue, masterGuid); System.out.print("."); } } } catch (Exception e) { System.out.println("Exception writing the initial value: " + e); e.printStackTrace(); System.exit(1); } } } /** * Removes the guid when we're done. */ public void removeSubGuid() { try { // if the user specified one guid we just need to remove that one if (updateAlias != null) { clients[0].guidRemove(masterGuid, subGuids[0]); // } else { // for (int i = 0; i < numberOfGuids; i++) { // clients[0].guidRemove(masterGuid, subGuids[i]); // System.out.println("Removed: " + subGuids[i]); // } } } catch (Exception e) { System.out.println("Exception when we were not expecting it: " + e); e.printStackTrace(); System.exit(1); } } /** * This generates GNS accesses starting at approximately the rate specified (per second) and * increasing buy increment per second every 5 seconds. * * @param startRate * @param increment * @param requestsPerClient */ private void ramp(int startRate, int increment, int requestsPerClient) { if (true) throw new RuntimeException("Disabled"); for (int ratePerSec = startRate;; ratePerSec = ratePerSec + increment) { for (int i = 0; i < numberOfClients; i++) { //clients[0].resetInstrumentation(); } System.out.print(Format.formatDateTimeOnly(new Date()) + " Attempted rate/s " + ratePerSec); // the calculated delay after sending a burst of requests int delayInMilleSeconds = numberOfClients * requestsPerClient * 1000 / ratePerSec; long loopStartTime = System.currentTimeMillis(); int numberSent = 0; long elapsedTimeInMilleSeconds; int sleepResolutionIssues = 0; do { // submit tasks at the prescribed rate for 5 seconds long initTime = System.currentTimeMillis(); try { // send a bunch for (int i = 0; i < numberOfClients; i++) { execPool.submit(new WorkerTask(i, requestsPerClient)); numberSent += requestsPerClient; if (numberSent % 1000 == 0) { System.out.print("."); } //} } } catch (Exception e) { System.out.println("Problem running field read: " + e); } // calculate and sleep long enough to satisfy our rate requirements long sleepTime = delayInMilleSeconds - (System.currentTimeMillis() - initTime); if (sleepTime > minSleepInterval) { ThreadUtils.sleep(sleepTime); } else { sleepResolutionIssues++; } // keep looping until CYCLE_TIME milleseconds have elapsed elapsedTimeInMilleSeconds = System.currentTimeMillis() - loopStartTime; } while (elapsedTimeInMilleSeconds < CYCLE_TIME); // calculate some total stats from the clients int outstandingPacketCount = 0; int totalErrors = 0; double latencySum = 0; for (int i = 0; i < numberOfClients; i++) { outstandingPacketCount += 0;//clients[i].outstandingAsynchPacketCount(); //totalErrors += clients[i].getTotalAsynchErrors(); //latencySum += clients[i].getMovingAvgLatency(); } System.out.println("\n" + Format.formatDateTimeOnly(new Date()) + " Actual rate/s: " + numberSent / (elapsedTimeInMilleSeconds / 1000.0) + " Average latency: " + Format.formatFloat(latencySum / numberOfClients) + " Outstanding packets: " + outstandingPacketCount + " Errors: " + totalErrors + (sleepResolutionIssues > 0 ? " Sleep Issues: " + sleepResolutionIssues : "") + "\n" + DelayProfiler.getStats()); if (outstandingPacketCount > 200000) { System.out.println("Backing off before networking freezes."); break; } } } // // Stuff below here is mostly currently for testing only // private static CommandPacket createReadCommandPacket(GNSClient client, String targetGuid, String field, GuidEntry reader) throws Exception { JSONObject command; if (reader == null) { command = createCommand(CommandType.ReadUnsigned, GNSProtocol.GUID.toString(), targetGuid, GNSProtocol.FIELD.toString(), field, GNSProtocol.READER.toString(), null); } else { command = createAndSignCommand(CommandType.Read, reader, GNSProtocol.GUID.toString(), targetGuid, GNSProtocol.FIELD.toString(), field, GNSProtocol.READER.toString(), reader.getGuid()); } // arun: can not reset requestID return new CommandPacket((long) (Math.random() * Long.MAX_VALUE), command); } private static CommandPacket createUpdateCommandPacket(GNSClient client, String targetGuid, JSONObject json, GuidEntry writer) throws Exception { JSONObject command; command = createAndSignCommand(CommandType.ReplaceUserJSON, writer, GNSProtocol.GUID.toString(), targetGuid, json.toString(), GNSProtocol.WRITER.toString(), writer.getGuid()); return new CommandPacket(-1, command); } /** * WorkerTask. */ public class WorkerTask implements Runnable { private final int clientNumber; private final int requests; Random rand = new Random(); /** * * @param clientNumber * @param requests */ public WorkerTask(int clientNumber, int requests) { this.clientNumber = clientNumber; this.requests = requests; } @Override public void run() { try { for (int j = 0; j < requests; j++) { int index; do { index = rand.nextInt(numberOfGuids); } while (chosen.contains(index)); chosen.add(index); // important to set the request id to something unique for the client // arun: nope, requestID is final, can not change // arun: disabled if (true) throw new RuntimeException("disabled"); // commmandPackets[index][clientNumber].setClientRequestId(clients[clientNumber].generateNextRequestID()); //clients[clientNumber].sendCommandPacketAsynch(commmandPackets[index][clientNumber]); // clear this out if we have used all the guids if (chosen.size() == numberOfGuids) { chosen.clear(); } } } catch (Exception e) { System.out.println("Problem running field read: " + e); } } } // command line arguments // COMMAND LINE STUFF private static HelpFormatter formatter = new HelpFormatter(); private static Options commandLineOptions; private static CommandLine initializeOptions(String[] args) throws ParseException { Option helpOption = new Option("help", "Prints Usage"); Option aliasOption = OptionBuilder.withArgName("alias").hasArg().withDescription("the alias (HRN) to use") .create("alias"); Option operationOption = OptionBuilder.withArgName("op").hasArg() .withDescription("the operation to perform (read or update - default is read)").create("op"); Option rateOption = OptionBuilder.withArgName("rate").hasArg().withDescription("the rate in ops per second") .create("rate"); Option incOption = OptionBuilder.withArgName("inc").hasArg().withDescription("the increment used with rate") .create("inc"); Option clientsOption = OptionBuilder.withArgName("clients").hasArg() .withDescription("number of clients used to send (default 10)").create("clients"); Option requestsPerClientOption = OptionBuilder.withArgName("requests").hasArg() .withDescription("number of requests sent by each client each time").create("requests"); Option guidsPerRequestOption = OptionBuilder.withArgName("guids").hasArg() .withDescription("number of guids for each request").create("guids"); Option useExistingGuidsOption = new Option("useExistingGuids", "use guids in account Guid instead of creating new ones"); Option updateAliasOption = new Option("updateAlias", true, "Alias of guid to update/read"); Option updateFieldOption = new Option("updateField", true, "Field to read/update"); Option updateValueOption = new Option("updateValue", true, "Value to use in read/update"); commandLineOptions = new Options(); commandLineOptions.addOption(aliasOption); commandLineOptions.addOption(operationOption); commandLineOptions.addOption(rateOption); commandLineOptions.addOption(incOption); commandLineOptions.addOption(clientsOption); commandLineOptions.addOption(requestsPerClientOption); commandLineOptions.addOption(guidsPerRequestOption); commandLineOptions.addOption(helpOption); commandLineOptions.addOption(updateAliasOption); commandLineOptions.addOption(updateFieldOption); commandLineOptions.addOption(updateValueOption); commandLineOptions.addOption(useExistingGuidsOption); CommandLineParser parser = new GnuParser(); return parser.parse(commandLineOptions, args); } private static void printUsage() { formatter.printHelp( "./scripts/client/runClient edu.umass.cs.gnsclient.client.testing.ThroughputAsynchMultiClientTest <options>", commandLineOptions); } }