/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package org.apache.nutch.tools; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; 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.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.util.StringUtils; import org.apache.nutch.searcher.Hits; import org.apache.nutch.searcher.NutchBean; import org.apache.nutch.searcher.Query; import org.apache.nutch.util.NutchConfiguration; /** * <p>A simple tool to perform load testing on configured search servers. A * queries file can be specified with a list of different queries to run against * the search servers. The number of threads used to perform concurrent * searches is also configurable.</p> * * <p>This tool will output approximate times for running all queries in the * queries file. If configured it will also print out individual queries times * to the log.</p> */ public class SearchLoadTester { public static final Log LOG = LogFactory.getLog(SearchLoadTester.class); private String queriesFile = null; private int numThreads = 100; private boolean showTimes = false; private ExecutorService pool = null; private static AtomicInteger numTotal = new AtomicInteger(0); private static AtomicInteger numErrored = new AtomicInteger(0); private static AtomicInteger numResolved = new AtomicInteger(0); private static AtomicLong totalTime = new AtomicLong(0L); private static Configuration conf = null; private static NutchBean bean = null; private static class SearchThread extends Thread { private String query = null; private boolean showTimes = false; public SearchThread(String query, boolean showTimes) { this.query = query; this.showTimes = showTimes; } public void run() { numTotal.incrementAndGet(); try { Query runner = Query.parse(query, conf); long start = System.currentTimeMillis(); Hits hits = bean.search(runner, 10); long end = System.currentTimeMillis(); numResolved.incrementAndGet(); long total = (end - start); if (showTimes) { System.out.println("Query for " + query + " numhits " + hits.getTotal() + " in " + total + "ms"); } totalTime.addAndGet(total); } catch (Exception uhe) { LOG.info("Error executing search for " + query); numErrored.incrementAndGet(); } } } public void testSearch() { try { // create a thread pool with a fixed number of threads pool = Executors.newFixedThreadPool(numThreads); // read in the queries file and loop through each line, one query per line BufferedReader buffRead = new BufferedReader(new FileReader(new File( queriesFile))); String queryStr = null; while ((queryStr = buffRead.readLine()) != null) { pool.execute(new SearchThread(queryStr, showTimes)); } // close the file and wait for up to 60 seconds before shutting down // the thread pool to give urls time to finish resolving buffRead.close(); pool.shutdown(); pool.awaitTermination(60, TimeUnit.SECONDS); LOG.info("Total Queries: " + numTotal.get() + ", Errored: " + numErrored.get() + ", Total Time: " + totalTime.get() + ", Average Time: " + totalTime.get() / numTotal.get() + " with " + numThreads + " threads"); } catch (Exception e) { e.printStackTrace(); // on error shutdown the thread pool immediately pool.shutdownNow(); LOG.info(StringUtils.stringifyException(e)); } } public SearchLoadTester(String queriesFile) throws IOException { this(queriesFile, 100, false); } public SearchLoadTester(String queriesFile, int numThreads, boolean showTimes) throws IOException { this.queriesFile = queriesFile; this.numThreads = numThreads; this.showTimes = showTimes; this.conf = NutchConfiguration.create(); this.bean = new NutchBean(conf); } public static void main(String[] args) { Options options = new Options(); Option helpOpts = OptionBuilder.withArgName("help").withDescription( "show this help message").create("help"); Option queriesOpts = OptionBuilder.withArgName("queries").hasArg().withDescription( "the queries file to test").create("queries"); Option numThreadOpts = OptionBuilder.withArgName("numThreads").hasArgs().withDescription( "the number of threads to use").create("numThreads"); Option showTimesOpts = OptionBuilder.withArgName("showTimes").withDescription( "show individual query times").create("showTimes"); options.addOption(helpOpts); options.addOption(queriesOpts); options.addOption(numThreadOpts); options.addOption(showTimesOpts); CommandLineParser parser = new GnuParser(); try { // parse out common line arguments CommandLine line = parser.parse(options, args); if (line.hasOption("help") || !line.hasOption("queries")) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("SearchTester", options); return; } // get the urls and the number of threads and start the resolver boolean showTimes = line.hasOption("showTimes"); String queries = line.getOptionValue("queries"); int numThreads = 10; String numThreadsStr = line.getOptionValue("numThreads"); if (numThreadsStr != null) { numThreads = Integer.parseInt(numThreadsStr); } SearchLoadTester tester = new SearchLoadTester(queries, numThreads, showTimes); tester.testSearch(); } catch (Exception e) { LOG.fatal("SearchTester: " + StringUtils.stringifyException(e)); } } }