001 /*
002 * Copyright 2010-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2010-2017 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk.examples;
022
023
024
025 import java.io.IOException;
026 import java.io.OutputStream;
027 import java.io.Serializable;
028 import java.text.ParseException;
029 import java.util.ArrayList;
030 import java.util.LinkedHashMap;
031 import java.util.LinkedHashSet;
032 import java.util.List;
033 import java.util.Random;
034 import java.util.concurrent.CyclicBarrier;
035 import java.util.concurrent.atomic.AtomicBoolean;
036 import java.util.concurrent.atomic.AtomicLong;
037
038 import com.unboundid.ldap.sdk.Control;
039 import com.unboundid.ldap.sdk.LDAPConnection;
040 import com.unboundid.ldap.sdk.LDAPConnectionOptions;
041 import com.unboundid.ldap.sdk.LDAPException;
042 import com.unboundid.ldap.sdk.ResultCode;
043 import com.unboundid.ldap.sdk.SearchScope;
044 import com.unboundid.ldap.sdk.Version;
045 import com.unboundid.ldap.sdk.controls.AssertionRequestControl;
046 import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl;
047 import com.unboundid.ldap.sdk.controls.PreReadRequestControl;
048 import com.unboundid.ldap.sdk.controls.PostReadRequestControl;
049 import com.unboundid.util.ColumnFormatter;
050 import com.unboundid.util.FixedRateBarrier;
051 import com.unboundid.util.FormattableColumn;
052 import com.unboundid.util.HorizontalAlignment;
053 import com.unboundid.util.LDAPCommandLineTool;
054 import com.unboundid.util.ObjectPair;
055 import com.unboundid.util.OutputFormat;
056 import com.unboundid.util.RateAdjustor;
057 import com.unboundid.util.ResultCodeCounter;
058 import com.unboundid.util.ThreadSafety;
059 import com.unboundid.util.ThreadSafetyLevel;
060 import com.unboundid.util.ValuePattern;
061 import com.unboundid.util.WakeableSleeper;
062 import com.unboundid.util.args.ArgumentException;
063 import com.unboundid.util.args.ArgumentParser;
064 import com.unboundid.util.args.BooleanArgument;
065 import com.unboundid.util.args.ControlArgument;
066 import com.unboundid.util.args.FileArgument;
067 import com.unboundid.util.args.FilterArgument;
068 import com.unboundid.util.args.IntegerArgument;
069 import com.unboundid.util.args.ScopeArgument;
070 import com.unboundid.util.args.StringArgument;
071
072 import static com.unboundid.util.Debug.*;
073 import static com.unboundid.util.StaticUtils.*;
074
075
076
077 /**
078 * This class provides a tool that can be used to search an LDAP directory
079 * server repeatedly using multiple threads, and then modify each entry
080 * returned by that server. It can help provide an estimate of the combined
081 * search and modify performance that a directory server is able to achieve.
082 * Either or both of the base DN and the search filter may be a value pattern as
083 * described in the {@link ValuePattern} class. This makes it possible to
084 * search over a range of entries rather than repeatedly performing searches
085 * with the same base DN and filter.
086 * <BR><BR>
087 * Some of the APIs demonstrated by this example include:
088 * <UL>
089 * <LI>Argument Parsing (from the {@code com.unboundid.util.args}
090 * package)</LI>
091 * <LI>LDAP Command-Line Tool (from the {@code com.unboundid.util}
092 * package)</LI>
093 * <LI>LDAP Communication (from the {@code com.unboundid.ldap.sdk}
094 * package)</LI>
095 * <LI>Value Patterns (from the {@code com.unboundid.util} package)</LI>
096 * </UL>
097 * <BR><BR>
098 * All of the necessary information is provided using command line arguments.
099 * Supported arguments include those allowed by the {@link LDAPCommandLineTool}
100 * class, as well as the following additional arguments:
101 * <UL>
102 * <LI>"-b {baseDN}" or "--baseDN {baseDN}" -- specifies the base DN to use
103 * for the searches. This must be provided. It may be a simple DN, or it
104 * may be a value pattern to express a range of base DNs.</LI>
105 * <LI>"-s {scope}" or "--scope {scope}" -- specifies the scope to use for the
106 * search. The scope value should be one of "base", "one", "sub", or
107 * "subord". If this isn't specified, then a scope of "sub" will be
108 * used.</LI>
109 * <LI>"-f {filter}" or "--filter {filter}" -- specifies the filter to use for
110 * the searches. This must be provided. It may be a simple filter, or it
111 * may be a value pattern to express a range of filters.</LI>
112 * <LI>"-A {name}" or "--attribute {name}" -- specifies the name of an
113 * attribute that should be included in entries returned from the server.
114 * If this is not provided, then all user attributes will be requested.
115 * This may include special tokens that the server may interpret, like
116 * "1.1" to indicate that no attributes should be returned, "*", for all
117 * user attributes, or "+" for all operational attributes. Multiple
118 * attributes may be requested with multiple instances of this
119 * argument.</LI>
120 * <LI>"-m {name}" or "--modifyAttribute {name}" -- specifies the name of the
121 * attribute to modify. Multiple attributes may be modified by providing
122 * multiple instances of this argument. At least one attribute must be
123 * provided.</LI>
124 * <LI>"-l {num}" or "--valueLength {num}" -- specifies the length in bytes to
125 * use for the values of the target attributes to modify. If this is not
126 * provided, then a default length of 10 bytes will be used.</LI>
127 * <LI>"-C {chars}" or "--characterSet {chars}" -- specifies the set of
128 * characters that will be used to generate the values to use for the
129 * target attributes to modify. It should only include ASCII characters.
130 * Values will be generated from randomly-selected characters from this
131 * set. If this is not provided, then a default set of lowercase
132 * alphabetic characters will be used.</LI>
133 * <LI>"-t {num}" or "--numThreads {num}" -- specifies the number of
134 * concurrent threads to use when performing the searches. If this is not
135 * provided, then a default of one thread will be used.</LI>
136 * <LI>"-i {sec}" or "--intervalDuration {sec}" -- specifies the length of
137 * time in seconds between lines out output. If this is not provided,
138 * then a default interval duration of five seconds will be used.</LI>
139 * <LI>"-I {num}" or "--numIntervals {num}" -- specifies the maximum number of
140 * intervals for which to run. If this is not provided, then it will
141 * run forever.</LI>
142 * <LI>"--iterationsBeforeReconnect {num}" -- specifies the number of search
143 * iterations that should be performed on a connection before that
144 * connection is closed and replaced with a newly-established (and
145 * authenticated, if appropriate) connection.</LI>
146 * <LI>"-r {ops-per-second}" or "--ratePerSecond {ops-per-second}" --
147 * specifies the target number of operations to perform per second. Each
148 * search and modify operation will be counted separately for this
149 * purpose, so if a value of 1 is specified and a search returns two
150 * entries, then a total of three seconds will be required (one for the
151 * search and one for the modify for each entry). It is still necessary
152 * to specify a sufficient number of threads for achieving this rate. If
153 * this option is not provided, then the tool will run at the maximum rate
154 * for the specified number of threads.</LI>
155 * <LI>"--variableRateData {path}" -- specifies the path to a file containing
156 * information needed to allow the tool to vary the target rate over time.
157 * If this option is not provided, then the tool will either use a fixed
158 * target rate as specified by the "--ratePerSecond" argument, or it will
159 * run at the maximum rate.</LI>
160 * <LI>"--generateSampleRateFile {path}" -- specifies the path to a file to
161 * which sample data will be written illustrating and describing the
162 * format of the file expected to be used in conjunction with the
163 * "--variableRateData" argument.</LI>
164 * <LI>"--warmUpIntervals {num}" -- specifies the number of intervals to
165 * complete before beginning overall statistics collection.</LI>
166 * <LI>"--timestampFormat {format}" -- specifies the format to use for
167 * timestamps included before each output line. The format may be one of
168 * "none" (for no timestamps), "with-date" (to include both the date and
169 * the time), or "without-date" (to include only time time).</LI>
170 * <LI>"-Y {authzID}" or "--proxyAs {authzID}" -- Use the proxied
171 * authorization v2 control to request that the operations be processed
172 * using an alternate authorization identity. In this case, the bind DN
173 * should be that of a user that has permission to use this control. The
174 * authorization identity may be a value pattern.</LI>
175 * <LI>"--suppressErrorResultCodes" -- Indicates that information about the
176 * result codes for failed operations should not be displayed.</LI>
177 * <LI>"-c" or "--csv" -- Generate output in CSV format rather than a
178 * display-friendly format.</LI>
179 * </UL>
180 */
181 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
182 public final class SearchAndModRate
183 extends LDAPCommandLineTool
184 implements Serializable
185 {
186 /**
187 * The serial version UID for this serializable class.
188 */
189 private static final long serialVersionUID = 3242469381380526294L;
190
191
192
193 // Indicates whether a request has been made to stop running.
194 private final AtomicBoolean stopRequested;
195
196 // The argument used to indicate whether to generate output in CSV format.
197 private BooleanArgument csvFormat;
198
199 // Indicates that modify requests should include the permissive modify request
200 // control.
201 private BooleanArgument permissiveModify;
202
203 // The argument used to indicate whether to suppress information about error
204 // result codes.
205 private BooleanArgument suppressErrors;
206
207 // The argument used to specify a set of generic controls to include in modify
208 // requests.
209 private ControlArgument modifyControl;
210
211 // The argument used to specify a set of generic controls to include in search
212 // requests.
213 private ControlArgument searchControl;
214
215 // The argument used to specify a variable rate file.
216 private FileArgument sampleRateFile;
217
218 // The argument used to specify a variable rate file.
219 private FileArgument variableRateData;
220
221 // The argument used to specify an LDAP assertion filter for modify requests.
222 private FilterArgument modifyAssertionFilter;
223
224 // The argument used to specify an LDAP assertion filter for search requests.
225 private FilterArgument searchAssertionFilter;
226
227 // The argument used to specify the collection interval.
228 private IntegerArgument collectionInterval;
229
230 // The argument used to specify the number of search and modify iterations on
231 // a connection before it is closed and re-established.
232 private IntegerArgument iterationsBeforeReconnect;
233
234 // The argument used to specify the number of intervals.
235 private IntegerArgument numIntervals;
236
237 // The argument used to specify the number of threads.
238 private IntegerArgument numThreads;
239
240 // The argument used to specify the seed to use for the random number
241 // generator.
242 private IntegerArgument randomSeed;
243
244 // The target rate of operations per second.
245 private IntegerArgument ratePerSecond;
246
247 // The argument used to indicate that the search should use the simple paged
248 // results control with the specified page size.
249 private IntegerArgument simplePageSize;
250
251 // The argument used to specify the length of the values to generate.
252 private IntegerArgument valueLength;
253
254 // The number of warm-up intervals to perform.
255 private IntegerArgument warmUpIntervals;
256
257 // The argument used to specify the scope for the searches.
258 private ScopeArgument scopeArg;
259
260 // The argument used to specify the base DNs for the searches.
261 private StringArgument baseDN;
262
263 // The argument used to specify the set of characters to use when generating
264 // values.
265 private StringArgument characterSet;
266
267 // The argument used to specify the filters for the searches.
268 private StringArgument filter;
269
270 // The argument used to specify the attributes to modify.
271 private StringArgument modifyAttributes;
272
273 // Indicates that modify requests should include the post-read request control
274 // to request the specified attribute.
275 private StringArgument postReadAttribute;
276
277 // Indicates that modify requests should include the pre-read request control
278 // to request the specified attribute.
279 private StringArgument preReadAttribute;
280
281 // The argument used to specify the proxied authorization identity.
282 private StringArgument proxyAs;
283
284 // The argument used to specify the attributes to return.
285 private StringArgument returnAttributes;
286
287 // The argument used to specify the timestamp format.
288 private StringArgument timestampFormat;
289
290 // The thread currently being used to run the searchrate tool.
291 private volatile Thread runningThread;
292
293 // A wakeable sleeper that will be used to sleep between reporting intervals.
294 private final WakeableSleeper sleeper;
295
296
297
298 /**
299 * Parse the provided command line arguments and make the appropriate set of
300 * changes.
301 *
302 * @param args The command line arguments provided to this program.
303 */
304 public static void main(final String[] args)
305 {
306 final ResultCode resultCode = main(args, System.out, System.err);
307 if (resultCode != ResultCode.SUCCESS)
308 {
309 System.exit(resultCode.intValue());
310 }
311 }
312
313
314
315 /**
316 * Parse the provided command line arguments and make the appropriate set of
317 * changes.
318 *
319 * @param args The command line arguments provided to this program.
320 * @param outStream The output stream to which standard out should be
321 * written. It may be {@code null} if output should be
322 * suppressed.
323 * @param errStream The output stream to which standard error should be
324 * written. It may be {@code null} if error messages
325 * should be suppressed.
326 *
327 * @return A result code indicating whether the processing was successful.
328 */
329 public static ResultCode main(final String[] args,
330 final OutputStream outStream,
331 final OutputStream errStream)
332 {
333 final SearchAndModRate searchAndModRate =
334 new SearchAndModRate(outStream, errStream);
335 return searchAndModRate.runTool(args);
336 }
337
338
339
340 /**
341 * Creates a new instance of this tool.
342 *
343 * @param outStream The output stream to which standard out should be
344 * written. It may be {@code null} if output should be
345 * suppressed.
346 * @param errStream The output stream to which standard error should be
347 * written. It may be {@code null} if error messages
348 * should be suppressed.
349 */
350 public SearchAndModRate(final OutputStream outStream,
351 final OutputStream errStream)
352 {
353 super(outStream, errStream);
354
355 stopRequested = new AtomicBoolean(false);
356 sleeper = new WakeableSleeper();
357 }
358
359
360
361 /**
362 * Retrieves the name for this tool.
363 *
364 * @return The name for this tool.
365 */
366 @Override()
367 public String getToolName()
368 {
369 return "search-and-mod-rate";
370 }
371
372
373
374 /**
375 * Retrieves the description for this tool.
376 *
377 * @return The description for this tool.
378 */
379 @Override()
380 public String getToolDescription()
381 {
382 return "Perform repeated searches against an " +
383 "LDAP directory server and modify each entry returned.";
384 }
385
386
387
388 /**
389 * Retrieves the version string for this tool.
390 *
391 * @return The version string for this tool.
392 */
393 @Override()
394 public String getToolVersion()
395 {
396 return Version.NUMERIC_VERSION_STRING;
397 }
398
399
400
401 /**
402 * Indicates whether this tool should provide support for an interactive mode,
403 * in which the tool offers a mode in which the arguments can be provided in
404 * a text-driven menu rather than requiring them to be given on the command
405 * line. If interactive mode is supported, it may be invoked using the
406 * "--interactive" argument. Alternately, if interactive mode is supported
407 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then
408 * interactive mode may be invoked by simply launching the tool without any
409 * arguments.
410 *
411 * @return {@code true} if this tool supports interactive mode, or
412 * {@code false} if not.
413 */
414 @Override()
415 public boolean supportsInteractiveMode()
416 {
417 return true;
418 }
419
420
421
422 /**
423 * Indicates whether this tool defaults to launching in interactive mode if
424 * the tool is invoked without any command-line arguments. This will only be
425 * used if {@link #supportsInteractiveMode()} returns {@code true}.
426 *
427 * @return {@code true} if this tool defaults to using interactive mode if
428 * launched without any command-line arguments, or {@code false} if
429 * not.
430 */
431 @Override()
432 public boolean defaultsToInteractiveMode()
433 {
434 return true;
435 }
436
437
438
439 /**
440 * Indicates whether this tool should provide arguments for redirecting output
441 * to a file. If this method returns {@code true}, then the tool will offer
442 * an "--outputFile" argument that will specify the path to a file to which
443 * all standard output and standard error content will be written, and it will
444 * also offer a "--teeToStandardOut" argument that can only be used if the
445 * "--outputFile" argument is present and will cause all output to be written
446 * to both the specified output file and to standard output.
447 *
448 * @return {@code true} if this tool should provide arguments for redirecting
449 * output to a file, or {@code false} if not.
450 */
451 @Override()
452 protected boolean supportsOutputFile()
453 {
454 return true;
455 }
456
457
458
459 /**
460 * Indicates whether this tool should default to interactively prompting for
461 * the bind password if a password is required but no argument was provided
462 * to indicate how to get the password.
463 *
464 * @return {@code true} if this tool should default to interactively
465 * prompting for the bind password, or {@code false} if not.
466 */
467 @Override()
468 protected boolean defaultToPromptForBindPassword()
469 {
470 return true;
471 }
472
473
474
475 /**
476 * Indicates whether this tool supports the use of a properties file for
477 * specifying default values for arguments that aren't specified on the
478 * command line.
479 *
480 * @return {@code true} if this tool supports the use of a properties file
481 * for specifying default values for arguments that aren't specified
482 * on the command line, or {@code false} if not.
483 */
484 @Override()
485 public boolean supportsPropertiesFile()
486 {
487 return true;
488 }
489
490
491
492 /**
493 * Indicates whether the LDAP-specific arguments should include alternate
494 * versions of all long identifiers that consist of multiple words so that
495 * they are available in both camelCase and dash-separated versions.
496 *
497 * @return {@code true} if this tool should provide multiple versions of
498 * long identifiers for LDAP-specific arguments, or {@code false} if
499 * not.
500 */
501 @Override()
502 protected boolean includeAlternateLongIdentifiers()
503 {
504 return true;
505 }
506
507
508
509 /**
510 * Adds the arguments used by this program that aren't already provided by the
511 * generic {@code LDAPCommandLineTool} framework.
512 *
513 * @param parser The argument parser to which the arguments should be added.
514 *
515 * @throws ArgumentException If a problem occurs while adding the arguments.
516 */
517 @Override()
518 public void addNonLDAPArguments(final ArgumentParser parser)
519 throws ArgumentException
520 {
521 String description = "The base DN to use for the searches. It may be a " +
522 "simple DN or a value pattern to specify a range of DNs (e.g., " +
523 "\"uid=user.[1-1000],ou=People,dc=example,dc=com\"). See " +
524 ValuePattern.PUBLIC_JAVADOC_URL + " for complete details about the " +
525 "value pattern syntax. This must be provided.";
526 baseDN = new StringArgument('b', "baseDN", true, 1, "{dn}", description);
527 baseDN.setArgumentGroupName("Search And Modification Arguments");
528 baseDN.addLongIdentifier("base-dn");
529 parser.addArgument(baseDN);
530
531
532 description = "The scope to use for the searches. It should be 'base', " +
533 "'one', 'sub', or 'subord'. If this is not provided, then " +
534 "a default scope of 'sub' will be used.";
535 scopeArg = new ScopeArgument('s', "scope", false, "{scope}", description,
536 SearchScope.SUB);
537 scopeArg.setArgumentGroupName("Search And Modification Arguments");
538 parser.addArgument(scopeArg);
539
540
541 description = "The filter to use for the searches. It may be a simple " +
542 "filter or a value pattern to specify a range of filters " +
543 "(e.g., \"(uid=user.[1-1000])\"). See " +
544 ValuePattern.PUBLIC_JAVADOC_URL + " for complete details " +
545 "about the value pattern syntax. This must be provided.";
546 filter = new StringArgument('f', "filter", true, 1, "{filter}",
547 description);
548 filter.setArgumentGroupName("Search And Modification Arguments");
549 parser.addArgument(filter);
550
551
552 description = "The name of an attribute to include in entries returned " +
553 "from the searches. Multiple attributes may be requested " +
554 "by providing this argument multiple times. If no request " +
555 "attributes are provided, then the entries returned will " +
556 "include all user attributes.";
557 returnAttributes = new StringArgument('A', "attribute", false, 0, "{name}",
558 description);
559 returnAttributes.setArgumentGroupName("Search And Modification Arguments");
560 parser.addArgument(returnAttributes);
561
562
563 description = "The name of the attribute to modify. Multiple attributes " +
564 "may be specified by providing this argument multiple " +
565 "times. At least one attribute must be specified.";
566 modifyAttributes = new StringArgument('m', "modifyAttribute", true, 0,
567 "{name}", description);
568 modifyAttributes.setArgumentGroupName("Search And Modification Arguments");
569 modifyAttributes.addLongIdentifier("modify-attribute");
570 parser.addArgument(modifyAttributes);
571
572
573 description = "The length in bytes to use when generating values for the " +
574 "modifications. If this is not provided, then a default " +
575 "length of ten bytes will be used.";
576 valueLength = new IntegerArgument('l', "valueLength", true, 1, "{num}",
577 description, 1, Integer.MAX_VALUE, 10);
578 valueLength.setArgumentGroupName("Search And Modification Arguments");
579 valueLength.addLongIdentifier("value-length");
580 parser.addArgument(valueLength);
581
582
583 description = "The set of characters to use to generate the values for " +
584 "the modifications. It should only include ASCII " +
585 "characters. If this is not provided, then a default set " +
586 "of lowercase alphabetic characters will be used.";
587 characterSet = new StringArgument('C', "characterSet", true, 1, "{chars}",
588 description,
589 "abcdefghijklmnopqrstuvwxyz");
590 characterSet.setArgumentGroupName("Search And Modification Arguments");
591 characterSet.addLongIdentifier("character-set");
592 parser.addArgument(characterSet);
593
594
595 description = "Indicates that search requests should include the " +
596 "assertion request control with the specified filter.";
597 searchAssertionFilter = new FilterArgument(null, "searchAssertionFilter",
598 false, 1, "{filter}",
599 description);
600 searchAssertionFilter.setArgumentGroupName("Request Control Arguments");
601 searchAssertionFilter.addLongIdentifier("search-assertion-filter");
602 parser.addArgument(searchAssertionFilter);
603
604
605 description = "Indicates that modify requests should include the " +
606 "assertion request control with the specified filter.";
607 modifyAssertionFilter = new FilterArgument(null, "modifyAssertionFilter",
608 false, 1, "{filter}",
609 description);
610 modifyAssertionFilter.setArgumentGroupName("Request Control Arguments");
611 modifyAssertionFilter.addLongIdentifier("modify-assertion-filter");
612 parser.addArgument(modifyAssertionFilter);
613
614
615 description = "Indicates that search requests should include the simple " +
616 "paged results control with the specified page size.";
617 simplePageSize = new IntegerArgument(null, "simplePageSize", false, 1,
618 "{size}", description, 1,
619 Integer.MAX_VALUE);
620 simplePageSize.setArgumentGroupName("Request Control Arguments");
621 simplePageSize.addLongIdentifier("simple-page-size");
622 parser.addArgument(simplePageSize);
623
624
625 description = "Indicates that modify requests should include the " +
626 "permissive modify request control.";
627 permissiveModify = new BooleanArgument(null, "permissiveModify", 1,
628 description);
629 permissiveModify.setArgumentGroupName("Request Control Arguments");
630 permissiveModify.addLongIdentifier("permissive-modify");
631 parser.addArgument(permissiveModify);
632
633
634 description = "Indicates that modify requests should include the " +
635 "pre-read request control with the specified requested " +
636 "attribute. This argument may be provided multiple times " +
637 "to indicate that multiple requested attributes should be " +
638 "included in the pre-read request control.";
639 preReadAttribute = new StringArgument(null, "preReadAttribute", false, 0,
640 "{attribute}", description);
641 preReadAttribute.setArgumentGroupName("Request Control Arguments");
642 preReadAttribute.addLongIdentifier("pre-read-attribute");
643 parser.addArgument(preReadAttribute);
644
645
646 description = "Indicates that modify requests should include the " +
647 "post-read request control with the specified requested " +
648 "attribute. This argument may be provided multiple times " +
649 "to indicate that multiple requested attributes should be " +
650 "included in the post-read request control.";
651 postReadAttribute = new StringArgument(null, "postReadAttribute", false, 0,
652 "{attribute}", description);
653 postReadAttribute.setArgumentGroupName("Request Control Arguments");
654 postReadAttribute.addLongIdentifier("post-read-attribute");
655 parser.addArgument(postReadAttribute);
656
657
658 description = "Indicates that the proxied authorization control (as " +
659 "defined in RFC 4370) should be used to request that " +
660 "operations be processed using an alternate authorization " +
661 "identity. This may be a simple authorization ID or it " +
662 "may be a value pattern to specify a range of " +
663 "identities. See " + ValuePattern.PUBLIC_JAVADOC_URL +
664 " for complete details about the value pattern syntax.";
665 proxyAs = new StringArgument('Y', "proxyAs", false, 1, "{authzID}",
666 description);
667 proxyAs.setArgumentGroupName("Request Control Arguments");
668 proxyAs.addLongIdentifier("proxy-as");
669 parser.addArgument(proxyAs);
670
671
672 description = "Indicates that search requests should include the " +
673 "specified request control. This may be provided multiple " +
674 "times to include multiple search request controls.";
675 searchControl = new ControlArgument(null, "searchControl", false, 0, null,
676 description);
677 searchControl.setArgumentGroupName("Request Control Arguments");
678 searchControl.addLongIdentifier("search-control");
679 parser.addArgument(searchControl);
680
681
682 description = "Indicates that modify requests should include the " +
683 "specified request control. This may be provided multiple " +
684 "times to include multiple modify request controls.";
685 modifyControl = new ControlArgument(null, "modifyControl", false, 0, null,
686 description);
687 modifyControl.setArgumentGroupName("Request Control Arguments");
688 modifyControl.addLongIdentifier("modify-control");
689 parser.addArgument(modifyControl);
690
691
692 description = "The number of threads to use to perform the searches. If " +
693 "this is not provided, then a default of one thread will " +
694 "be used.";
695 numThreads = new IntegerArgument('t', "numThreads", true, 1, "{num}",
696 description, 1, Integer.MAX_VALUE, 1);
697 numThreads.setArgumentGroupName("Rate Management Arguments");
698 numThreads.addLongIdentifier("num-threads");
699 parser.addArgument(numThreads);
700
701
702 description = "The length of time in seconds between output lines. If " +
703 "this is not provided, then a default interval of five " +
704 "seconds will be used.";
705 collectionInterval = new IntegerArgument('i', "intervalDuration", true, 1,
706 "{num}", description, 1,
707 Integer.MAX_VALUE, 5);
708 collectionInterval.setArgumentGroupName("Rate Management Arguments");
709 collectionInterval.addLongIdentifier("interval-duration");
710 parser.addArgument(collectionInterval);
711
712
713 description = "The maximum number of intervals for which to run. If " +
714 "this is not provided, then the tool will run until it is " +
715 "interrupted.";
716 numIntervals = new IntegerArgument('I', "numIntervals", true, 1, "{num}",
717 description, 1, Integer.MAX_VALUE,
718 Integer.MAX_VALUE);
719 numIntervals.setArgumentGroupName("Rate Management Arguments");
720 numIntervals.addLongIdentifier("num-intervals");
721 parser.addArgument(numIntervals);
722
723 description = "The number of search and modify iterations that should be " +
724 "processed on a connection before that connection is " +
725 "closed and replaced with a newly-established (and " +
726 "authenticated, if appropriate) connection. If this is " +
727 "not provided, then connections will not be periodically " +
728 "closed and re-established.";
729 iterationsBeforeReconnect = new IntegerArgument(null,
730 "iterationsBeforeReconnect", false, 1, "{num}", description, 0);
731 iterationsBeforeReconnect.setArgumentGroupName("Rate Management Arguments");
732 iterationsBeforeReconnect.addLongIdentifier("iterations-before-reconnect");
733 parser.addArgument(iterationsBeforeReconnect);
734
735 description = "The target number of searches to perform per second. It " +
736 "is still necessary to specify a sufficient number of " +
737 "threads for achieving this rate. If neither this option " +
738 "nor --variableRateData is provided, then the tool will " +
739 "run at the maximum rate for the specified number of " +
740 "threads.";
741 ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1,
742 "{searches-per-second}", description,
743 1, Integer.MAX_VALUE);
744 ratePerSecond.setArgumentGroupName("Rate Management Arguments");
745 ratePerSecond.addLongIdentifier("rate-per-second");
746 parser.addArgument(ratePerSecond);
747
748 final String variableRateDataArgName = "variableRateData";
749 final String generateSampleRateFileArgName = "generateSampleRateFile";
750 description = RateAdjustor.getVariableRateDataArgumentDescription(
751 generateSampleRateFileArgName);
752 variableRateData = new FileArgument(null, variableRateDataArgName, false, 1,
753 "{path}", description, true, true, true,
754 false);
755 variableRateData.setArgumentGroupName("Rate Management Arguments");
756 variableRateData.addLongIdentifier("variable-rate-data");
757 parser.addArgument(variableRateData);
758
759 description = RateAdjustor.getGenerateSampleVariableRateFileDescription(
760 variableRateDataArgName);
761 sampleRateFile = new FileArgument(null, generateSampleRateFileArgName,
762 false, 1, "{path}", description, false,
763 true, true, false);
764 sampleRateFile.setArgumentGroupName("Rate Management Arguments");
765 sampleRateFile.addLongIdentifier("generate-sample-rate-file");
766 sampleRateFile.setUsageArgument(true);
767 parser.addArgument(sampleRateFile);
768 parser.addExclusiveArgumentSet(variableRateData, sampleRateFile);
769
770 description = "The number of intervals to complete before beginning " +
771 "overall statistics collection. Specifying a nonzero " +
772 "number of warm-up intervals gives the client and server " +
773 "a chance to warm up without skewing performance results.";
774 warmUpIntervals = new IntegerArgument(null, "warmUpIntervals", true, 1,
775 "{num}", description, 0, Integer.MAX_VALUE, 0);
776 warmUpIntervals.setArgumentGroupName("Rate Management Arguments");
777 warmUpIntervals.addLongIdentifier("warm-up-intervals");
778 parser.addArgument(warmUpIntervals);
779
780 description = "Indicates the format to use for timestamps included in " +
781 "the output. A value of 'none' indicates that no " +
782 "timestamps should be included. A value of 'with-date' " +
783 "indicates that both the date and the time should be " +
784 "included. A value of 'without-date' indicates that only " +
785 "the time should be included.";
786 final LinkedHashSet<String> allowedFormats = new LinkedHashSet<String>(3);
787 allowedFormats.add("none");
788 allowedFormats.add("with-date");
789 allowedFormats.add("without-date");
790 timestampFormat = new StringArgument(null, "timestampFormat", true, 1,
791 "{format}", description, allowedFormats, "none");
792 timestampFormat.addLongIdentifier("timestamp-format");
793 parser.addArgument(timestampFormat);
794
795 description = "Indicates that information about the result codes for " +
796 "failed operations should not be displayed.";
797 suppressErrors = new BooleanArgument(null,
798 "suppressErrorResultCodes", 1, description);
799 suppressErrors.addLongIdentifier("suppress-error-result-codes");
800 parser.addArgument(suppressErrors);
801
802 description = "Generate output in CSV format rather than a " +
803 "display-friendly format";
804 csvFormat = new BooleanArgument('c', "csv", 1, description);
805 parser.addArgument(csvFormat);
806
807 description = "Specifies the seed to use for the random number generator.";
808 randomSeed = new IntegerArgument('R', "randomSeed", false, 1, "{value}",
809 description);
810 randomSeed.addLongIdentifier("random-seed");
811 parser.addArgument(randomSeed);
812 }
813
814
815
816 /**
817 * Indicates whether this tool supports creating connections to multiple
818 * servers. If it is to support multiple servers, then the "--hostname" and
819 * "--port" arguments will be allowed to be provided multiple times, and
820 * will be required to be provided the same number of times. The same type of
821 * communication security and bind credentials will be used for all servers.
822 *
823 * @return {@code true} if this tool supports creating connections to
824 * multiple servers, or {@code false} if not.
825 */
826 @Override()
827 protected boolean supportsMultipleServers()
828 {
829 return true;
830 }
831
832
833
834 /**
835 * Retrieves the connection options that should be used for connections
836 * created for use with this tool.
837 *
838 * @return The connection options that should be used for connections created
839 * for use with this tool.
840 */
841 @Override()
842 public LDAPConnectionOptions getConnectionOptions()
843 {
844 final LDAPConnectionOptions options = new LDAPConnectionOptions();
845 options.setUseSynchronousMode(true);
846 return options;
847 }
848
849
850
851 /**
852 * Performs the actual processing for this tool. In this case, it gets a
853 * connection to the directory server and uses it to perform the requested
854 * searches.
855 *
856 * @return The result code for the processing that was performed.
857 */
858 @Override()
859 public ResultCode doToolProcessing()
860 {
861 runningThread = Thread.currentThread();
862
863 try
864 {
865 return doToolProcessingInternal();
866 }
867 finally
868 {
869 runningThread = null;
870 }
871 }
872
873
874
875 /**
876 * Performs the actual processing for this tool. In this case, it gets a
877 * connection to the directory server and uses it to perform the requested
878 * searches.
879 *
880 * @return The result code for the processing that was performed.
881 */
882 private ResultCode doToolProcessingInternal()
883 {
884 // If the sample rate file argument was specified, then generate the sample
885 // variable rate data file and return.
886 if (sampleRateFile.isPresent())
887 {
888 try
889 {
890 RateAdjustor.writeSampleVariableRateFile(sampleRateFile.getValue());
891 return ResultCode.SUCCESS;
892 }
893 catch (final Exception e)
894 {
895 debugException(e);
896 err("An error occurred while trying to write sample variable data " +
897 "rate file '", sampleRateFile.getValue().getAbsolutePath(),
898 "': ", getExceptionMessage(e));
899 return ResultCode.LOCAL_ERROR;
900 }
901 }
902
903
904 // Determine the random seed to use.
905 final Long seed;
906 if (randomSeed.isPresent())
907 {
908 seed = Long.valueOf(randomSeed.getValue());
909 }
910 else
911 {
912 seed = null;
913 }
914
915 // Create value patterns for the base DN, filter, and proxied authorization
916 // DN.
917 final ValuePattern dnPattern;
918 try
919 {
920 dnPattern = new ValuePattern(baseDN.getValue(), seed);
921 }
922 catch (final ParseException pe)
923 {
924 debugException(pe);
925 err("Unable to parse the base DN value pattern: ", pe.getMessage());
926 return ResultCode.PARAM_ERROR;
927 }
928
929 final ValuePattern filterPattern;
930 try
931 {
932 filterPattern = new ValuePattern(filter.getValue(), seed);
933 }
934 catch (final ParseException pe)
935 {
936 debugException(pe);
937 err("Unable to parse the filter pattern: ", pe.getMessage());
938 return ResultCode.PARAM_ERROR;
939 }
940
941 final ValuePattern authzIDPattern;
942 if (proxyAs.isPresent())
943 {
944 try
945 {
946 authzIDPattern = new ValuePattern(proxyAs.getValue(), seed);
947 }
948 catch (final ParseException pe)
949 {
950 debugException(pe);
951 err("Unable to parse the proxied authorization pattern: ",
952 pe.getMessage());
953 return ResultCode.PARAM_ERROR;
954 }
955 }
956 else
957 {
958 authzIDPattern = null;
959 }
960
961
962 // Get the set of controls to include in search requests.
963 final ArrayList<Control> searchControls = new ArrayList<Control>(5);
964 if (searchAssertionFilter.isPresent())
965 {
966 searchControls.add(new AssertionRequestControl(
967 searchAssertionFilter.getValue()));
968 }
969
970 if (searchControl.isPresent())
971 {
972 searchControls.addAll(searchControl.getValues());
973 }
974
975
976 // Get the set of controls to include in modify requests.
977 final ArrayList<Control> modifyControls = new ArrayList<Control>(5);
978 if (modifyAssertionFilter.isPresent())
979 {
980 modifyControls.add(new AssertionRequestControl(
981 modifyAssertionFilter.getValue()));
982 }
983
984 if (permissiveModify.isPresent())
985 {
986 modifyControls.add(new PermissiveModifyRequestControl());
987 }
988
989 if (preReadAttribute.isPresent())
990 {
991 final List<String> attrList = preReadAttribute.getValues();
992 final String[] attrArray = new String[attrList.size()];
993 attrList.toArray(attrArray);
994 modifyControls.add(new PreReadRequestControl(attrArray));
995 }
996
997 if (postReadAttribute.isPresent())
998 {
999 final List<String> attrList = postReadAttribute.getValues();
1000 final String[] attrArray = new String[attrList.size()];
1001 attrList.toArray(attrArray);
1002 modifyControls.add(new PostReadRequestControl(attrArray));
1003 }
1004
1005 if (modifyControl.isPresent())
1006 {
1007 modifyControls.addAll(modifyControl.getValues());
1008 }
1009
1010
1011 // Get the attributes to return.
1012 final String[] returnAttrs;
1013 if (returnAttributes.isPresent())
1014 {
1015 final List<String> attrList = returnAttributes.getValues();
1016 returnAttrs = new String[attrList.size()];
1017 attrList.toArray(returnAttrs);
1018 }
1019 else
1020 {
1021 returnAttrs = NO_STRINGS;
1022 }
1023
1024
1025 // Get the names of the attributes to modify.
1026 final String[] modAttrs = new String[modifyAttributes.getValues().size()];
1027 modifyAttributes.getValues().toArray(modAttrs);
1028
1029
1030 // Get the character set as a byte array.
1031 final byte[] charSet = getBytes(characterSet.getValue());
1032
1033
1034 // If the --ratePerSecond option was specified, then limit the rate
1035 // accordingly.
1036 FixedRateBarrier fixedRateBarrier = null;
1037 if (ratePerSecond.isPresent() || variableRateData.isPresent())
1038 {
1039 // We might not have a rate per second if --variableRateData is specified.
1040 // The rate typically doesn't matter except when we have warm-up
1041 // intervals. In this case, we'll run at the max rate.
1042 final int intervalSeconds = collectionInterval.getValue();
1043 final int ratePerInterval =
1044 (ratePerSecond.getValue() == null)
1045 ? Integer.MAX_VALUE
1046 : ratePerSecond.getValue() * intervalSeconds;
1047 fixedRateBarrier =
1048 new FixedRateBarrier(1000L * intervalSeconds, ratePerInterval);
1049 }
1050
1051
1052 // If --variableRateData was specified, then initialize a RateAdjustor.
1053 RateAdjustor rateAdjustor = null;
1054 if (variableRateData.isPresent())
1055 {
1056 try
1057 {
1058 rateAdjustor = RateAdjustor.newInstance(fixedRateBarrier,
1059 ratePerSecond.getValue(), variableRateData.getValue());
1060 }
1061 catch (final IOException e)
1062 {
1063 debugException(e);
1064 err("Initializing the variable rates failed: " + e.getMessage());
1065 return ResultCode.PARAM_ERROR;
1066 }
1067 catch (final IllegalArgumentException e)
1068 {
1069 debugException(e);
1070 err("Initializing the variable rates failed: " + e.getMessage());
1071 return ResultCode.PARAM_ERROR;
1072 }
1073 }
1074
1075
1076 // Determine whether to include timestamps in the output and if so what
1077 // format should be used for them.
1078 final boolean includeTimestamp;
1079 final String timeFormat;
1080 if (timestampFormat.getValue().equalsIgnoreCase("with-date"))
1081 {
1082 includeTimestamp = true;
1083 timeFormat = "dd/MM/yyyy HH:mm:ss";
1084 }
1085 else if (timestampFormat.getValue().equalsIgnoreCase("without-date"))
1086 {
1087 includeTimestamp = true;
1088 timeFormat = "HH:mm:ss";
1089 }
1090 else
1091 {
1092 includeTimestamp = false;
1093 timeFormat = null;
1094 }
1095
1096
1097 // Determine whether any warm-up intervals should be run.
1098 final long totalIntervals;
1099 final boolean warmUp;
1100 int remainingWarmUpIntervals = warmUpIntervals.getValue();
1101 if (remainingWarmUpIntervals > 0)
1102 {
1103 warmUp = true;
1104 totalIntervals = 0L + numIntervals.getValue() + remainingWarmUpIntervals;
1105 }
1106 else
1107 {
1108 warmUp = true;
1109 totalIntervals = 0L + numIntervals.getValue();
1110 }
1111
1112
1113 // Create the table that will be used to format the output.
1114 final OutputFormat outputFormat;
1115 if (csvFormat.isPresent())
1116 {
1117 outputFormat = OutputFormat.CSV;
1118 }
1119 else
1120 {
1121 outputFormat = OutputFormat.COLUMNS;
1122 }
1123
1124 final ColumnFormatter formatter = new ColumnFormatter(includeTimestamp,
1125 timeFormat, outputFormat, " ",
1126 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent",
1127 "Searches/Sec"),
1128 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent",
1129 "Srch Dur ms"),
1130 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent",
1131 "Mods/Sec"),
1132 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent",
1133 "Mod Dur ms"),
1134 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent",
1135 "Errors/Sec"),
1136 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall",
1137 "Searches/Sec"),
1138 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall",
1139 "Srch Dur ms"),
1140 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall",
1141 "Mods/Sec"),
1142 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall",
1143 "Mod Dur ms"));
1144
1145
1146 // Create values to use for statistics collection.
1147 final AtomicLong searchCounter = new AtomicLong(0L);
1148 final AtomicLong errorCounter = new AtomicLong(0L);
1149 final AtomicLong modCounter = new AtomicLong(0L);
1150 final AtomicLong modDurations = new AtomicLong(0L);
1151 final AtomicLong searchDurations = new AtomicLong(0L);
1152 final ResultCodeCounter rcCounter = new ResultCodeCounter();
1153
1154
1155 // Determine the length of each interval in milliseconds.
1156 final long intervalMillis = 1000L * collectionInterval.getValue();
1157
1158
1159 // Create the threads to use for the searches.
1160 final Random random = new Random();
1161 final CyclicBarrier barrier = new CyclicBarrier(numThreads.getValue() + 1);
1162 final SearchAndModRateThread[] threads =
1163 new SearchAndModRateThread[numThreads.getValue()];
1164 for (int i=0; i < threads.length; i++)
1165 {
1166 final LDAPConnection connection;
1167 try
1168 {
1169 connection = getConnection();
1170 }
1171 catch (final LDAPException le)
1172 {
1173 debugException(le);
1174 err("Unable to connect to the directory server: ",
1175 getExceptionMessage(le));
1176 return le.getResultCode();
1177 }
1178
1179 threads[i] = new SearchAndModRateThread(this, i, connection, dnPattern,
1180 scopeArg.getValue(), filterPattern, returnAttrs, modAttrs,
1181 valueLength.getValue(), charSet, authzIDPattern,
1182 simplePageSize.getValue(), searchControls, modifyControls,
1183 iterationsBeforeReconnect.getValue(), random.nextLong(), barrier,
1184 searchCounter, modCounter, searchDurations, modDurations,
1185 errorCounter, rcCounter, fixedRateBarrier);
1186 threads[i].start();
1187 }
1188
1189
1190 // Display the table header.
1191 for (final String headerLine : formatter.getHeaderLines(true))
1192 {
1193 out(headerLine);
1194 }
1195
1196
1197 // Start the RateAdjustor before the threads so that the initial value is
1198 // in place before any load is generated unless we're doing a warm-up in
1199 // which case, we'll start it after the warm-up is complete.
1200 if ((rateAdjustor != null) && (remainingWarmUpIntervals <= 0))
1201 {
1202 rateAdjustor.start();
1203 }
1204
1205
1206 // Indicate that the threads can start running.
1207 try
1208 {
1209 barrier.await();
1210 }
1211 catch (final Exception e)
1212 {
1213 debugException(e);
1214 }
1215
1216 long overallStartTime = System.nanoTime();
1217 long nextIntervalStartTime = System.currentTimeMillis() + intervalMillis;
1218
1219
1220 boolean setOverallStartTime = false;
1221 long lastSearchDuration = 0L;
1222 long lastModDuration = 0L;
1223 long lastNumErrors = 0L;
1224 long lastNumSearches = 0L;
1225 long lastNumMods = 0L;
1226 long lastEndTime = System.nanoTime();
1227 for (long i=0; i < totalIntervals; i++)
1228 {
1229 if (rateAdjustor != null)
1230 {
1231 if (! rateAdjustor.isAlive())
1232 {
1233 out("All of the rates in " + variableRateData.getValue().getName() +
1234 " have been completed.");
1235 break;
1236 }
1237 }
1238
1239 final long startTimeMillis = System.currentTimeMillis();
1240 final long sleepTimeMillis = nextIntervalStartTime - startTimeMillis;
1241 nextIntervalStartTime += intervalMillis;
1242 if (sleepTimeMillis > 0)
1243 {
1244 sleeper.sleep(sleepTimeMillis);
1245 }
1246
1247 if (stopRequested.get())
1248 {
1249 break;
1250 }
1251
1252 final long endTime = System.nanoTime();
1253 final long intervalDuration = endTime - lastEndTime;
1254
1255 final long numSearches;
1256 final long numMods;
1257 final long numErrors;
1258 final long totalSearchDuration;
1259 final long totalModDuration;
1260 if (warmUp && (remainingWarmUpIntervals > 0))
1261 {
1262 numSearches = searchCounter.getAndSet(0L);
1263 numMods = modCounter.getAndSet(0L);
1264 numErrors = errorCounter.getAndSet(0L);
1265 totalSearchDuration = searchDurations.getAndSet(0L);
1266 totalModDuration = modDurations.getAndSet(0L);
1267 }
1268 else
1269 {
1270 numSearches = searchCounter.get();
1271 numMods = modCounter.get();
1272 numErrors = errorCounter.get();
1273 totalSearchDuration = searchDurations.get();
1274 totalModDuration = modDurations.get();
1275 }
1276
1277 final long recentNumSearches = numSearches - lastNumSearches;
1278 final long recentNumMods = numMods - lastNumMods;
1279 final long recentNumErrors = numErrors - lastNumErrors;
1280 final long recentSearchDuration =
1281 totalSearchDuration - lastSearchDuration;
1282 final long recentModDuration = totalModDuration - lastModDuration;
1283
1284 final double numSeconds = intervalDuration / 1000000000.0d;
1285 final double recentSearchRate = recentNumSearches / numSeconds;
1286 final double recentModRate = recentNumMods / numSeconds;
1287 final double recentErrorRate = recentNumErrors / numSeconds;
1288
1289 final double recentAvgSearchDuration;
1290 if (recentNumSearches > 0L)
1291 {
1292 recentAvgSearchDuration =
1293 1.0d * recentSearchDuration / recentNumSearches / 1000000;
1294 }
1295 else
1296 {
1297 recentAvgSearchDuration = 0.0d;
1298 }
1299
1300 final double recentAvgModDuration;
1301 if (recentNumMods > 0L)
1302 {
1303 recentAvgModDuration =
1304 1.0d * recentModDuration / recentNumMods / 1000000;
1305 }
1306 else
1307 {
1308 recentAvgModDuration = 0.0d;
1309 }
1310
1311 if (warmUp && (remainingWarmUpIntervals > 0))
1312 {
1313 out(formatter.formatRow(recentSearchRate, recentAvgSearchDuration,
1314 recentModRate, recentAvgModDuration, recentErrorRate, "warming up",
1315 "warming up", "warming up", "warming up"));
1316
1317 remainingWarmUpIntervals--;
1318 if (remainingWarmUpIntervals == 0)
1319 {
1320 out("Warm-up completed. Beginning overall statistics collection.");
1321 setOverallStartTime = true;
1322 if (rateAdjustor != null)
1323 {
1324 rateAdjustor.start();
1325 }
1326 }
1327 }
1328 else
1329 {
1330 if (setOverallStartTime)
1331 {
1332 overallStartTime = lastEndTime;
1333 setOverallStartTime = false;
1334 }
1335
1336 final double numOverallSeconds =
1337 (endTime - overallStartTime) / 1000000000.0d;
1338 final double overallSearchRate = numSearches / numOverallSeconds;
1339 final double overallModRate = numMods / numOverallSeconds;
1340
1341 final double overallAvgSearchDuration;
1342 if (numSearches > 0L)
1343 {
1344 overallAvgSearchDuration =
1345 1.0d * totalSearchDuration / numSearches / 1000000;
1346 }
1347 else
1348 {
1349 overallAvgSearchDuration = 0.0d;
1350 }
1351
1352 final double overallAvgModDuration;
1353 if (numMods > 0L)
1354 {
1355 overallAvgModDuration =
1356 1.0d * totalModDuration / numMods / 1000000;
1357 }
1358 else
1359 {
1360 overallAvgModDuration = 0.0d;
1361 }
1362
1363 out(formatter.formatRow(recentSearchRate, recentAvgSearchDuration,
1364 recentModRate, recentAvgModDuration, recentErrorRate,
1365 overallSearchRate, overallAvgSearchDuration, overallModRate,
1366 overallAvgModDuration));
1367
1368 lastNumSearches = numSearches;
1369 lastNumMods = numMods;
1370 lastNumErrors = numErrors;
1371 lastSearchDuration = totalSearchDuration;
1372 lastModDuration = totalModDuration;
1373 }
1374
1375 final List<ObjectPair<ResultCode,Long>> rcCounts =
1376 rcCounter.getCounts(true);
1377 if ((! suppressErrors.isPresent()) && (! rcCounts.isEmpty()))
1378 {
1379 err("\tError Results:");
1380 for (final ObjectPair<ResultCode,Long> p : rcCounts)
1381 {
1382 err("\t", p.getFirst().getName(), ": ", p.getSecond());
1383 }
1384 }
1385
1386 lastEndTime = endTime;
1387 }
1388
1389
1390 // Shut down the RateAdjustor if we have one.
1391 if (rateAdjustor != null)
1392 {
1393 rateAdjustor.shutDown();
1394 }
1395
1396 // Stop all of the threads.
1397 ResultCode resultCode = ResultCode.SUCCESS;
1398 for (final SearchAndModRateThread t : threads)
1399 {
1400 final ResultCode r = t.stopRunning();
1401 if (resultCode == ResultCode.SUCCESS)
1402 {
1403 resultCode = r;
1404 }
1405 }
1406
1407 return resultCode;
1408 }
1409
1410
1411
1412 /**
1413 * Requests that this tool stop running. This method will attempt to wait
1414 * for all threads to complete before returning control to the caller.
1415 */
1416 public void stopRunning()
1417 {
1418 stopRequested.set(true);
1419 sleeper.wakeup();
1420
1421 final Thread t = runningThread;
1422 if (t != null)
1423 {
1424 try
1425 {
1426 t.join();
1427 }
1428 catch (final Exception e)
1429 {
1430 debugException(e);
1431
1432 if (e instanceof InterruptedException)
1433 {
1434 Thread.currentThread().interrupt();
1435 }
1436 }
1437 }
1438 }
1439
1440
1441
1442 /**
1443 * {@inheritDoc}
1444 */
1445 @Override()
1446 public LinkedHashMap<String[],String> getExampleUsages()
1447 {
1448 final LinkedHashMap<String[],String> examples =
1449 new LinkedHashMap<String[],String>(2);
1450
1451 String[] args =
1452 {
1453 "--hostname", "server.example.com",
1454 "--port", "389",
1455 "--bindDN", "uid=admin,dc=example,dc=com",
1456 "--bindPassword", "password",
1457 "--baseDN", "dc=example,dc=com",
1458 "--scope", "sub",
1459 "--filter", "(uid=user.[1-1000000])",
1460 "--attribute", "givenName",
1461 "--attribute", "sn",
1462 "--attribute", "mail",
1463 "--modifyAttribute", "description",
1464 "--valueLength", "10",
1465 "--characterSet", "abcdefghijklmnopqrstuvwxyz0123456789",
1466 "--numThreads", "10"
1467 };
1468 String description =
1469 "Test search and modify performance by searching randomly across a " +
1470 "set of one million users located below 'dc=example,dc=com' with " +
1471 "ten concurrent threads. The entries returned to the client will " +
1472 "include the givenName, sn, and mail attributes, and the " +
1473 "description attribute of each entry returned will be replaced " +
1474 "with a string of ten randomly-selected alphanumeric characters.";
1475 examples.put(args, description);
1476
1477 args = new String[]
1478 {
1479 "--generateSampleRateFile", "variable-rate-data.txt"
1480 };
1481 description =
1482 "Generate a sample variable rate definition file that may be used " +
1483 "in conjunction with the --variableRateData argument. The sample " +
1484 "file will include comments that describe the format for data to be " +
1485 "included in this file.";
1486 examples.put(args, description);
1487
1488 return examples;
1489 }
1490 }