/******************************************************************************* * Copyright (c) 2008 Cambridge Semantics Incorporated. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cambridge Semantics Incorporated *******************************************************************************/ package org.openanzo.client.cli; import java.io.BufferedReader; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.lang.StringUtils; import org.openanzo.analysis.RequestParser; import org.openanzo.client.AnzoClient; import org.openanzo.datasource.IAuthorizationService; import org.openanzo.datasource.IModelService; import org.openanzo.datasource.IQueryService; import org.openanzo.datasource.IReplicationService; import org.openanzo.datasource.IResetService; import org.openanzo.datasource.IUpdateService; import org.openanzo.exceptions.AnzoException; import org.openanzo.rdf.utils.SmartEncodingInputStream; import org.openanzo.services.IExecutionService; import org.openanzo.services.INotificationRegistrationService; /** * Executes a SPARQL query against the server. * * @author Ben Szekely ( <a href="mailto:ben@cambridgesemantics.com">ben@cambridgesemantics.com </a>) * */ class PlayCommand extends RdfIOCommand { //private static final String NEWLINE_PATTERN = "\r\n|\r|\n"; //private static final Logger log = LoggerFactory.getLogger(PlayCommand.class); private static final Option REQUEST_INPUT_OPTION = new Option("f", "request-input", true, "Playback the requests in the given file"); private static final Option SERVICES_OPTION = new Option("s", "services", true, "Include only the given services (r)eplication, (q)uery, (m)odel, (a)authorization, (u)pdate, (n)otification, rese(t)"); private static final Option BODY_OPTION = new Option("b", "body-pattern", true, "A simple text-match pattern to filter requests based on the body"); //private static final Option PROPERTY_OPTION = new Option("r", "property-pattern", true, "A simple text-match pattern to filter request based on property values (disjunctively), example -r \"operationUri=http://cambridgesemantics.com/semanticServices/OntologyService#listClasses,jmsCorrelationId=1ef2wn8acq66me902t-10\""); private static final Option PROPERTY_OPTION = new Option("r", "property-pattern", true, "A simple text-match pattern to filter request based on property values (disjunctively)"); private static final Option CREDS_OPTION = new Option("c", "credential-file", true, "A name=value pair file with user/passwords for requests that use a different user"); private static final Option CSV_STATS = new Option("v", "csv-summary", false, "Outputs a CSV version of the summary playback time statistics"); private static final Option NUM_CLIENTS_OPTION = new Option("n", "num-clients", true, "Peform the playback with n concurrent clients"); private static final Option ENCODING = new Option("e", "encoding", true, "Override the default charset for uploading RDF files."); static { REQUEST_INPUT_OPTION.setArgName("file | URI"); } public String getName() { return "play"; } public Options getOptions() { Options options = new Options(); options.addOption(REQUEST_INPUT_OPTION); options.addOption(SERVICES_OPTION); options.addOption(BODY_OPTION); options.addOption(CREDS_OPTION); options.addOption(PROPERTY_OPTION); options.addOption(CSV_STATS); options.addOption(NUM_CLIENTS_OPTION); options.addOption(ENCODING); return options; } public int invoke(CommandLine cl, final CommandContext context, AnzoClient client) throws AnzoException { try { Set<String> allowedDestinations = null; String serviceFlags = cl.getOptionValue(SERVICES_OPTION.getOpt()); if (serviceFlags != null) { allowedDestinations = new HashSet<String>(); for (int i = 0; i < serviceFlags.length(); i++) { if (serviceFlags.charAt(i) == 'r') { allowedDestinations.add(IReplicationService.SERVICE_NAME); } else if (serviceFlags.charAt(i) == 'q') { allowedDestinations.add(IQueryService.SERVICE_NAME); } else if (serviceFlags.charAt(i) == 'm') { allowedDestinations.add(IModelService.SERVICE_NAME); } else if (serviceFlags.charAt(i) == 'a') { allowedDestinations.add(IAuthorizationService.SERVICE_NAME); } else if (serviceFlags.charAt(i) == 'n') { allowedDestinations.add(INotificationRegistrationService.SERVICE_NAME); } else if (serviceFlags.charAt(i) == 'u') { allowedDestinations.add(IUpdateService.SERVICE_NAME); } else if (serviceFlags.charAt(i) == 't') { allowedDestinations.add(IResetService.SERVICE_NAME); } else if (serviceFlags.charAt(i) == 'e') { allowedDestinations.add(IExecutionService.SERVICE_NAME); } } } else { // setup some default allowed destinations if necessary } String bodyPattern = cl.getOptionValue(BODY_OPTION.getOpt()); String charsetName = getEncodingOption(cl, ENCODING); HashMap<String, String> creds = new HashMap<String, String>(); if (cl.hasOption(CREDS_OPTION.getOpt())) { Reader credsInput = getRdfInputOption(context, cl, CREDS_OPTION, null, charsetName).getReader(); BufferedReader reader = new BufferedReader(credsInput); try { String line = reader.readLine(); while (line != null) { String[] parts = StringUtils.split(line, '='); creds.put(parts[0], parts[1]); line = reader.readLine(); } } finally { reader.close(); } } HashMap<String, List<String>> propFilter = null; if (cl.hasOption(PROPERTY_OPTION.getOpt())) { propFilter = new HashMap<String, List<String>>(); String str = cl.getOptionValue(PROPERTY_OPTION.getOpt()); String[] filters = StringUtils.split(str, ','); for (int i = 0; i < filters.length; i++) { String[] parts = StringUtils.split(filters[i], '='); List<String> vals = propFilter.get(parts[0]); if (vals == null) { vals = new ArrayList<String>(); propFilter.put(parts[0], vals); } vals.add(parts[1]); } } int numClients = 1; if (cl.hasOption(NUM_CLIENTS_OPTION.getOpt())) { numClients = Integer.parseInt(cl.getOptionValue(NUM_CLIENTS_OPTION.getOpt())); } context.writeOutput("numClients: " + numClients); final RequestParser parser = new RequestParser(); final PlaybackHandler[] handlers = new PlaybackHandler[numClients]; creds.put(context.getUser(), context.getPassword()); final Set<PlaybackHandler> completedHandlers = new HashSet<PlaybackHandler>(); final Boolean[] failed = { false }; for (int i = 0; i < numClients; i++) { handlers[i] = new PlaybackHandler(i, creds, context.getHost(), context.getPort(), context.getUseSsl(), context.getTimeout(), bodyPattern, allowedDestinations, propFilter); final int ind = i; Reader requestInput = null; if (!cl.hasOption(REQUEST_INPUT_OPTION.getOpt())) { requestInput = SmartEncodingInputStream.createSmartReader(System.in); } else { requestInput = getRdfInputOption(context, cl, REQUEST_INPUT_OPTION, null, charsetName).getReader(); } final Reader finalRequestInput = requestInput; final boolean hasCsvOpt = cl.hasOption(CSV_STATS.getOpt()); Runnable runner = new Runnable() { public void run() { System.out.println("Starting client (" + ind + ")"); try { parser.parseRequest(finalRequestInput, handlers[ind]); } catch (Exception e) { context.writeError("Error parsing request:" + e.getMessage()); if (context.showTrace) { e.printStackTrace(); } failed[0] = true; } outputSummaryStatistics(handlers[ind], hasCsvOpt); context.writeError("Handler completed: " + ind); completedHandlers.add(handlers[ind]); } }; if (numClients == 1) { runner.run(); } else { new Thread(runner).start(); } } while (completedHandlers.size() != numClients) { context.writeError("Completed handlers: " + completedHandlers.size()); Thread.sleep(200); if (failed[0]) { return 1; } } System.out.println("All clients have completed."); return 0; } catch (Exception e) { throw new CommandException(e, "play"); } } /** * Print out some statistics about the playback session. * * @param handler * - a handler that was already executed and has collected statistics about the playback. */ private void outputSummaryStatistics(PlaybackHandler handler, boolean outputCSV) { long endTime = System.currentTimeMillis(); StringBuilder csvHeaderStr = null; StringBuilder csvStatsStr = null; if (outputCSV) { csvHeaderStr = new StringBuilder("Total Time,Duration Sum"); csvStatsStr = new StringBuilder(""); csvStatsStr.append(endTime - handler.overallStartTime); csvStatsStr.append(","); csvStatsStr.append(handler.requestDurationTotal); } System.out.println("Total requests sent: " + handler.totalRequestsSent); System.out.println("Total time (ms): " + (endTime - handler.overallStartTime)); System.out.println("Sum of request durations (ms): " + handler.requestDurationTotal); System.out.println("Total Duration By Operation in ms (with % of sum):"); long longestOperationLength = 0; for (Map.Entry<String, Long> entry : handler.perOperationDurationTotals.entrySet()) { long len = entry.getKey().length(); if (len > longestOperationLength) { longestOperationLength = len; } } for (Map.Entry<String, Long> entry : handler.perOperationDurationTotals.entrySet()) { double ratio = (double) entry.getValue() / (double) handler.requestDurationTotal; System.out.format("\t %-" + (longestOperationLength + 1) + "s %d (%.2f%%)\n", entry.getKey() + ":", entry.getValue(), ratio * 100.0); if (csvHeaderStr != null && csvStatsStr != null) { csvHeaderStr.append(",Duration ("); csvHeaderStr.append(entry.getKey()); csvHeaderStr.append("),% Duration("); csvHeaderStr.append(entry.getKey()); csvHeaderStr.append(")"); csvStatsStr.append(","); csvStatsStr.append(entry.getValue()); csvStatsStr.append(","); csvStatsStr.append(String.format("%.4f", ratio)); } } System.out.println("Total Requests By Operation (with % of total):"); for (Map.Entry<String, Long> entry : handler.perOperationCounts.entrySet()) { double ratio = (double) entry.getValue() / (double) handler.totalRequestsSent; System.out.format("\t %-" + (longestOperationLength + 1) + "s %d (%.2f%%)\n", entry.getKey() + ":", entry.getValue(), ratio * 100.0); if (csvHeaderStr != null && csvStatsStr != null) { csvHeaderStr.append(",Request Count ("); csvHeaderStr.append(entry.getKey()); csvHeaderStr.append("),% Request Count("); csvHeaderStr.append(entry.getKey()); csvHeaderStr.append(")"); csvStatsStr.append(","); csvStatsStr.append(entry.getValue()); csvStatsStr.append(","); csvStatsStr.append(String.format("%.4f", ratio)); } } if (outputCSV) { System.out.println("CSV Summary Statistics:"); System.out.println(csvHeaderStr); System.out.println(csvStatsStr); } } public void printHelp(IConsole consoleWriter) { String header = "Playback a sequences of recorded requests specified by "; header += "the " + REQUEST_INPUT_OPTION.getLongOpt() + " option, if set. If not provided, the query is read from STDIN. "; String syntax = "anzo play [options] [-f REQUEST-INPUT-FILE ]"; String footer = "To include only replication and query services: anzo play -s qr request.txt"; Options options = getOptions(); CommandLineInterface.appendGlobalOptions(options); consoleWriter.printHelp( syntax, header, options, footer); } }