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