001/* 002 * Copyright 2010-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2010-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.Random; 034import java.util.concurrent.CyclicBarrier; 035import java.util.concurrent.atomic.AtomicBoolean; 036import java.util.concurrent.atomic.AtomicLong; 037 038import com.unboundid.ldap.sdk.Control; 039import com.unboundid.ldap.sdk.LDAPConnection; 040import com.unboundid.ldap.sdk.LDAPConnectionOptions; 041import com.unboundid.ldap.sdk.LDAPException; 042import com.unboundid.ldap.sdk.ResultCode; 043import com.unboundid.ldap.sdk.SearchScope; 044import com.unboundid.ldap.sdk.Version; 045import com.unboundid.ldap.sdk.controls.AssertionRequestControl; 046import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl; 047import com.unboundid.ldap.sdk.controls.PreReadRequestControl; 048import com.unboundid.ldap.sdk.controls.PostReadRequestControl; 049import com.unboundid.util.ColumnFormatter; 050import com.unboundid.util.FixedRateBarrier; 051import com.unboundid.util.FormattableColumn; 052import com.unboundid.util.HorizontalAlignment; 053import com.unboundid.util.LDAPCommandLineTool; 054import com.unboundid.util.ObjectPair; 055import com.unboundid.util.OutputFormat; 056import com.unboundid.util.RateAdjustor; 057import com.unboundid.util.ResultCodeCounter; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060import com.unboundid.util.ValuePattern; 061import com.unboundid.util.WakeableSleeper; 062import com.unboundid.util.args.ArgumentException; 063import com.unboundid.util.args.ArgumentParser; 064import com.unboundid.util.args.BooleanArgument; 065import com.unboundid.util.args.ControlArgument; 066import com.unboundid.util.args.FileArgument; 067import com.unboundid.util.args.FilterArgument; 068import com.unboundid.util.args.IntegerArgument; 069import com.unboundid.util.args.ScopeArgument; 070import com.unboundid.util.args.StringArgument; 071 072import static com.unboundid.util.Debug.*; 073import 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) 182public 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 * {@inheritDoc} 511 */ 512 @Override() 513 protected boolean logToolInvocationByDefault() 514 { 515 return true; 516 } 517 518 519 520 /** 521 * Adds the arguments used by this program that aren't already provided by the 522 * generic {@code LDAPCommandLineTool} framework. 523 * 524 * @param parser The argument parser to which the arguments should be added. 525 * 526 * @throws ArgumentException If a problem occurs while adding the arguments. 527 */ 528 @Override() 529 public void addNonLDAPArguments(final ArgumentParser parser) 530 throws ArgumentException 531 { 532 String description = "The base DN to use for the searches. It may be a " + 533 "simple DN or a value pattern to specify a range of DNs (e.g., " + 534 "\"uid=user.[1-1000],ou=People,dc=example,dc=com\"). See " + 535 ValuePattern.PUBLIC_JAVADOC_URL + " for complete details about the " + 536 "value pattern syntax. This must be provided."; 537 baseDN = new StringArgument('b', "baseDN", true, 1, "{dn}", description); 538 baseDN.setArgumentGroupName("Search And Modification Arguments"); 539 baseDN.addLongIdentifier("base-dn", true); 540 parser.addArgument(baseDN); 541 542 543 description = "The scope to use for the searches. It should be 'base', " + 544 "'one', 'sub', or 'subord'. If this is not provided, then " + 545 "a default scope of 'sub' will be used."; 546 scopeArg = new ScopeArgument('s', "scope", false, "{scope}", description, 547 SearchScope.SUB); 548 scopeArg.setArgumentGroupName("Search And Modification Arguments"); 549 parser.addArgument(scopeArg); 550 551 552 description = "The filter to use for the searches. It may be a simple " + 553 "filter or a value pattern to specify a range of filters " + 554 "(e.g., \"(uid=user.[1-1000])\"). See " + 555 ValuePattern.PUBLIC_JAVADOC_URL + " for complete details " + 556 "about the value pattern syntax. This must be provided."; 557 filter = new StringArgument('f', "filter", true, 1, "{filter}", 558 description); 559 filter.setArgumentGroupName("Search And Modification Arguments"); 560 parser.addArgument(filter); 561 562 563 description = "The name of an attribute to include in entries returned " + 564 "from the searches. Multiple attributes may be requested " + 565 "by providing this argument multiple times. If no request " + 566 "attributes are provided, then the entries returned will " + 567 "include all user attributes."; 568 returnAttributes = new StringArgument('A', "attribute", false, 0, "{name}", 569 description); 570 returnAttributes.setArgumentGroupName("Search And Modification Arguments"); 571 parser.addArgument(returnAttributes); 572 573 574 description = "The name of the attribute to modify. Multiple attributes " + 575 "may be specified by providing this argument multiple " + 576 "times. At least one attribute must be specified."; 577 modifyAttributes = new StringArgument('m', "modifyAttribute", true, 0, 578 "{name}", description); 579 modifyAttributes.setArgumentGroupName("Search And Modification Arguments"); 580 modifyAttributes.addLongIdentifier("modify-attribute", true); 581 parser.addArgument(modifyAttributes); 582 583 584 description = "The length in bytes to use when generating values for the " + 585 "modifications. If this is not provided, then a default " + 586 "length of ten bytes will be used."; 587 valueLength = new IntegerArgument('l', "valueLength", true, 1, "{num}", 588 description, 1, Integer.MAX_VALUE, 10); 589 valueLength.setArgumentGroupName("Search And Modification Arguments"); 590 valueLength.addLongIdentifier("value-length", true); 591 parser.addArgument(valueLength); 592 593 594 description = "The set of characters to use to generate the values for " + 595 "the modifications. It should only include ASCII " + 596 "characters. If this is not provided, then a default set " + 597 "of lowercase alphabetic characters will be used."; 598 characterSet = new StringArgument('C', "characterSet", true, 1, "{chars}", 599 description, 600 "abcdefghijklmnopqrstuvwxyz"); 601 characterSet.setArgumentGroupName("Search And Modification Arguments"); 602 characterSet.addLongIdentifier("character-set", true); 603 parser.addArgument(characterSet); 604 605 606 description = "Indicates that search requests should include the " + 607 "assertion request control with the specified filter."; 608 searchAssertionFilter = new FilterArgument(null, "searchAssertionFilter", 609 false, 1, "{filter}", 610 description); 611 searchAssertionFilter.setArgumentGroupName("Request Control Arguments"); 612 searchAssertionFilter.addLongIdentifier("search-assertion-filter", true); 613 parser.addArgument(searchAssertionFilter); 614 615 616 description = "Indicates that modify requests should include the " + 617 "assertion request control with the specified filter."; 618 modifyAssertionFilter = new FilterArgument(null, "modifyAssertionFilter", 619 false, 1, "{filter}", 620 description); 621 modifyAssertionFilter.setArgumentGroupName("Request Control Arguments"); 622 modifyAssertionFilter.addLongIdentifier("modify-assertion-filter", true); 623 parser.addArgument(modifyAssertionFilter); 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 modify requests should include the " + 637 "permissive modify request control."; 638 permissiveModify = new BooleanArgument(null, "permissiveModify", 1, 639 description); 640 permissiveModify.setArgumentGroupName("Request Control Arguments"); 641 permissiveModify.addLongIdentifier("permissive-modify", true); 642 parser.addArgument(permissiveModify); 643 644 645 description = "Indicates that modify requests should include the " + 646 "pre-read request control with the specified requested " + 647 "attribute. This argument may be provided multiple times " + 648 "to indicate that multiple requested attributes should be " + 649 "included in the pre-read request control."; 650 preReadAttribute = new StringArgument(null, "preReadAttribute", false, 0, 651 "{attribute}", description); 652 preReadAttribute.setArgumentGroupName("Request Control Arguments"); 653 preReadAttribute.addLongIdentifier("pre-read-attribute", true); 654 parser.addArgument(preReadAttribute); 655 656 657 description = "Indicates that modify requests should include the " + 658 "post-read request control with the specified requested " + 659 "attribute. This argument may be provided multiple times " + 660 "to indicate that multiple requested attributes should be " + 661 "included in the post-read request control."; 662 postReadAttribute = new StringArgument(null, "postReadAttribute", false, 0, 663 "{attribute}", description); 664 postReadAttribute.setArgumentGroupName("Request Control Arguments"); 665 postReadAttribute.addLongIdentifier("post-read-attribute", true); 666 parser.addArgument(postReadAttribute); 667 668 669 description = "Indicates that the proxied authorization control (as " + 670 "defined in RFC 4370) should be used to request that " + 671 "operations be processed using an alternate authorization " + 672 "identity. This may be a simple authorization ID or it " + 673 "may be a value pattern to specify a range of " + 674 "identities. See " + ValuePattern.PUBLIC_JAVADOC_URL + 675 " for complete details about the value pattern syntax."; 676 proxyAs = new StringArgument('Y', "proxyAs", false, 1, "{authzID}", 677 description); 678 proxyAs.setArgumentGroupName("Request Control Arguments"); 679 proxyAs.addLongIdentifier("proxy-as", true); 680 parser.addArgument(proxyAs); 681 682 683 description = "Indicates that search requests should include the " + 684 "specified request control. This may be provided multiple " + 685 "times to include multiple search request controls."; 686 searchControl = new ControlArgument(null, "searchControl", false, 0, null, 687 description); 688 searchControl.setArgumentGroupName("Request Control Arguments"); 689 searchControl.addLongIdentifier("search-control", true); 690 parser.addArgument(searchControl); 691 692 693 description = "Indicates that modify requests should include the " + 694 "specified request control. This may be provided multiple " + 695 "times to include multiple modify request controls."; 696 modifyControl = new ControlArgument(null, "modifyControl", false, 0, null, 697 description); 698 modifyControl.setArgumentGroupName("Request Control Arguments"); 699 modifyControl.addLongIdentifier("modify-control", true); 700 parser.addArgument(modifyControl); 701 702 703 description = "The number of threads to use to perform the searches. If " + 704 "this is not provided, then a default of one thread will " + 705 "be used."; 706 numThreads = new IntegerArgument('t', "numThreads", true, 1, "{num}", 707 description, 1, Integer.MAX_VALUE, 1); 708 numThreads.setArgumentGroupName("Rate Management Arguments"); 709 numThreads.addLongIdentifier("num-threads", true); 710 parser.addArgument(numThreads); 711 712 713 description = "The length of time in seconds between output lines. If " + 714 "this is not provided, then a default interval of five " + 715 "seconds will be used."; 716 collectionInterval = new IntegerArgument('i', "intervalDuration", true, 1, 717 "{num}", description, 1, 718 Integer.MAX_VALUE, 5); 719 collectionInterval.setArgumentGroupName("Rate Management Arguments"); 720 collectionInterval.addLongIdentifier("interval-duration", true); 721 parser.addArgument(collectionInterval); 722 723 724 description = "The maximum number of intervals for which to run. If " + 725 "this is not provided, then the tool will run until it is " + 726 "interrupted."; 727 numIntervals = new IntegerArgument('I', "numIntervals", true, 1, "{num}", 728 description, 1, Integer.MAX_VALUE, 729 Integer.MAX_VALUE); 730 numIntervals.setArgumentGroupName("Rate Management Arguments"); 731 numIntervals.addLongIdentifier("num-intervals", true); 732 parser.addArgument(numIntervals); 733 734 description = "The number of search and modify iterations that should be " + 735 "processed on a connection before that connection is " + 736 "closed and replaced with a newly-established (and " + 737 "authenticated, if appropriate) connection. If this is " + 738 "not provided, then connections will not be periodically " + 739 "closed and re-established."; 740 iterationsBeforeReconnect = new IntegerArgument(null, 741 "iterationsBeforeReconnect", false, 1, "{num}", description, 0); 742 iterationsBeforeReconnect.setArgumentGroupName("Rate Management Arguments"); 743 iterationsBeforeReconnect.addLongIdentifier("iterations-before-reconnect", 744 true); 745 parser.addArgument(iterationsBeforeReconnect); 746 747 description = "The target number of searches to perform per second. It " + 748 "is still necessary to specify a sufficient number of " + 749 "threads for achieving this rate. If neither this option " + 750 "nor --variableRateData is provided, then the tool will " + 751 "run at the maximum rate for the specified number of " + 752 "threads."; 753 ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1, 754 "{searches-per-second}", description, 755 1, Integer.MAX_VALUE); 756 ratePerSecond.setArgumentGroupName("Rate Management Arguments"); 757 ratePerSecond.addLongIdentifier("rate-per-second", true); 758 parser.addArgument(ratePerSecond); 759 760 final String variableRateDataArgName = "variableRateData"; 761 final String generateSampleRateFileArgName = "generateSampleRateFile"; 762 description = RateAdjustor.getVariableRateDataArgumentDescription( 763 generateSampleRateFileArgName); 764 variableRateData = new FileArgument(null, variableRateDataArgName, false, 1, 765 "{path}", description, true, true, true, 766 false); 767 variableRateData.setArgumentGroupName("Rate Management Arguments"); 768 variableRateData.addLongIdentifier("variable-rate-data", true); 769 parser.addArgument(variableRateData); 770 771 description = RateAdjustor.getGenerateSampleVariableRateFileDescription( 772 variableRateDataArgName); 773 sampleRateFile = new FileArgument(null, generateSampleRateFileArgName, 774 false, 1, "{path}", description, false, 775 true, true, false); 776 sampleRateFile.setArgumentGroupName("Rate Management Arguments"); 777 sampleRateFile.addLongIdentifier("generate-sample-rate-file", true); 778 sampleRateFile.setUsageArgument(true); 779 parser.addArgument(sampleRateFile); 780 parser.addExclusiveArgumentSet(variableRateData, sampleRateFile); 781 782 description = "The number of intervals to complete before beginning " + 783 "overall statistics collection. Specifying a nonzero " + 784 "number of warm-up intervals gives the client and server " + 785 "a chance to warm up without skewing performance results."; 786 warmUpIntervals = new IntegerArgument(null, "warmUpIntervals", true, 1, 787 "{num}", description, 0, Integer.MAX_VALUE, 0); 788 warmUpIntervals.setArgumentGroupName("Rate Management Arguments"); 789 warmUpIntervals.addLongIdentifier("warm-up-intervals", true); 790 parser.addArgument(warmUpIntervals); 791 792 description = "Indicates the format to use for timestamps included in " + 793 "the output. A value of 'none' indicates that no " + 794 "timestamps should be included. A value of 'with-date' " + 795 "indicates that both the date and the time should be " + 796 "included. A value of 'without-date' indicates that only " + 797 "the time should be included."; 798 final LinkedHashSet<String> allowedFormats = new LinkedHashSet<String>(3); 799 allowedFormats.add("none"); 800 allowedFormats.add("with-date"); 801 allowedFormats.add("without-date"); 802 timestampFormat = new StringArgument(null, "timestampFormat", true, 1, 803 "{format}", description, allowedFormats, "none"); 804 timestampFormat.addLongIdentifier("timestamp-format", true); 805 parser.addArgument(timestampFormat); 806 807 description = "Indicates that information about the result codes for " + 808 "failed operations should not be displayed."; 809 suppressErrors = new BooleanArgument(null, 810 "suppressErrorResultCodes", 1, description); 811 suppressErrors.addLongIdentifier("suppress-error-result-codes", true); 812 parser.addArgument(suppressErrors); 813 814 description = "Generate output in CSV format rather than a " + 815 "display-friendly format"; 816 csvFormat = new BooleanArgument('c', "csv", 1, description); 817 parser.addArgument(csvFormat); 818 819 description = "Specifies the seed to use for the random number generator."; 820 randomSeed = new IntegerArgument('R', "randomSeed", false, 1, "{value}", 821 description); 822 randomSeed.addLongIdentifier("random-seed", true); 823 parser.addArgument(randomSeed); 824 } 825 826 827 828 /** 829 * Indicates whether this tool supports creating connections to multiple 830 * servers. If it is to support multiple servers, then the "--hostname" and 831 * "--port" arguments will be allowed to be provided multiple times, and 832 * will be required to be provided the same number of times. The same type of 833 * communication security and bind credentials will be used for all servers. 834 * 835 * @return {@code true} if this tool supports creating connections to 836 * multiple servers, or {@code false} if not. 837 */ 838 @Override() 839 protected boolean supportsMultipleServers() 840 { 841 return true; 842 } 843 844 845 846 /** 847 * Retrieves the connection options that should be used for connections 848 * created for use with this tool. 849 * 850 * @return The connection options that should be used for connections created 851 * for use with this tool. 852 */ 853 @Override() 854 public LDAPConnectionOptions getConnectionOptions() 855 { 856 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 857 options.setUseSynchronousMode(true); 858 return options; 859 } 860 861 862 863 /** 864 * Performs the actual processing for this tool. In this case, it gets a 865 * connection to the directory server and uses it to perform the requested 866 * searches. 867 * 868 * @return The result code for the processing that was performed. 869 */ 870 @Override() 871 public ResultCode doToolProcessing() 872 { 873 runningThread = Thread.currentThread(); 874 875 try 876 { 877 return doToolProcessingInternal(); 878 } 879 finally 880 { 881 runningThread = null; 882 } 883 } 884 885 886 887 /** 888 * Performs the actual processing for this tool. In this case, it gets a 889 * connection to the directory server and uses it to perform the requested 890 * searches. 891 * 892 * @return The result code for the processing that was performed. 893 */ 894 private ResultCode doToolProcessingInternal() 895 { 896 // If the sample rate file argument was specified, then generate the sample 897 // variable rate data file and return. 898 if (sampleRateFile.isPresent()) 899 { 900 try 901 { 902 RateAdjustor.writeSampleVariableRateFile(sampleRateFile.getValue()); 903 return ResultCode.SUCCESS; 904 } 905 catch (final Exception e) 906 { 907 debugException(e); 908 err("An error occurred while trying to write sample variable data " + 909 "rate file '", sampleRateFile.getValue().getAbsolutePath(), 910 "': ", getExceptionMessage(e)); 911 return ResultCode.LOCAL_ERROR; 912 } 913 } 914 915 916 // Determine the random seed to use. 917 final Long seed; 918 if (randomSeed.isPresent()) 919 { 920 seed = Long.valueOf(randomSeed.getValue()); 921 } 922 else 923 { 924 seed = null; 925 } 926 927 // Create value patterns for the base DN, filter, and proxied authorization 928 // DN. 929 final ValuePattern dnPattern; 930 try 931 { 932 dnPattern = new ValuePattern(baseDN.getValue(), seed); 933 } 934 catch (final ParseException pe) 935 { 936 debugException(pe); 937 err("Unable to parse the base DN value pattern: ", pe.getMessage()); 938 return ResultCode.PARAM_ERROR; 939 } 940 941 final ValuePattern filterPattern; 942 try 943 { 944 filterPattern = new ValuePattern(filter.getValue(), seed); 945 } 946 catch (final ParseException pe) 947 { 948 debugException(pe); 949 err("Unable to parse the filter pattern: ", pe.getMessage()); 950 return ResultCode.PARAM_ERROR; 951 } 952 953 final ValuePattern authzIDPattern; 954 if (proxyAs.isPresent()) 955 { 956 try 957 { 958 authzIDPattern = new ValuePattern(proxyAs.getValue(), seed); 959 } 960 catch (final ParseException pe) 961 { 962 debugException(pe); 963 err("Unable to parse the proxied authorization pattern: ", 964 pe.getMessage()); 965 return ResultCode.PARAM_ERROR; 966 } 967 } 968 else 969 { 970 authzIDPattern = null; 971 } 972 973 974 // Get the set of controls to include in search requests. 975 final ArrayList<Control> searchControls = new ArrayList<Control>(5); 976 if (searchAssertionFilter.isPresent()) 977 { 978 searchControls.add(new AssertionRequestControl( 979 searchAssertionFilter.getValue())); 980 } 981 982 if (searchControl.isPresent()) 983 { 984 searchControls.addAll(searchControl.getValues()); 985 } 986 987 988 // Get the set of controls to include in modify requests. 989 final ArrayList<Control> modifyControls = new ArrayList<Control>(5); 990 if (modifyAssertionFilter.isPresent()) 991 { 992 modifyControls.add(new AssertionRequestControl( 993 modifyAssertionFilter.getValue())); 994 } 995 996 if (permissiveModify.isPresent()) 997 { 998 modifyControls.add(new PermissiveModifyRequestControl()); 999 } 1000 1001 if (preReadAttribute.isPresent()) 1002 { 1003 final List<String> attrList = preReadAttribute.getValues(); 1004 final String[] attrArray = new String[attrList.size()]; 1005 attrList.toArray(attrArray); 1006 modifyControls.add(new PreReadRequestControl(attrArray)); 1007 } 1008 1009 if (postReadAttribute.isPresent()) 1010 { 1011 final List<String> attrList = postReadAttribute.getValues(); 1012 final String[] attrArray = new String[attrList.size()]; 1013 attrList.toArray(attrArray); 1014 modifyControls.add(new PostReadRequestControl(attrArray)); 1015 } 1016 1017 if (modifyControl.isPresent()) 1018 { 1019 modifyControls.addAll(modifyControl.getValues()); 1020 } 1021 1022 1023 // Get the attributes to return. 1024 final String[] returnAttrs; 1025 if (returnAttributes.isPresent()) 1026 { 1027 final List<String> attrList = returnAttributes.getValues(); 1028 returnAttrs = new String[attrList.size()]; 1029 attrList.toArray(returnAttrs); 1030 } 1031 else 1032 { 1033 returnAttrs = NO_STRINGS; 1034 } 1035 1036 1037 // Get the names of the attributes to modify. 1038 final String[] modAttrs = new String[modifyAttributes.getValues().size()]; 1039 modifyAttributes.getValues().toArray(modAttrs); 1040 1041 1042 // Get the character set as a byte array. 1043 final byte[] charSet = getBytes(characterSet.getValue()); 1044 1045 1046 // If the --ratePerSecond option was specified, then limit the rate 1047 // accordingly. 1048 FixedRateBarrier fixedRateBarrier = null; 1049 if (ratePerSecond.isPresent() || variableRateData.isPresent()) 1050 { 1051 // We might not have a rate per second if --variableRateData is specified. 1052 // The rate typically doesn't matter except when we have warm-up 1053 // intervals. In this case, we'll run at the max rate. 1054 final int intervalSeconds = collectionInterval.getValue(); 1055 final int ratePerInterval = 1056 (ratePerSecond.getValue() == null) 1057 ? Integer.MAX_VALUE 1058 : ratePerSecond.getValue() * intervalSeconds; 1059 fixedRateBarrier = 1060 new FixedRateBarrier(1000L * intervalSeconds, ratePerInterval); 1061 } 1062 1063 1064 // If --variableRateData was specified, then initialize a RateAdjustor. 1065 RateAdjustor rateAdjustor = null; 1066 if (variableRateData.isPresent()) 1067 { 1068 try 1069 { 1070 rateAdjustor = RateAdjustor.newInstance(fixedRateBarrier, 1071 ratePerSecond.getValue(), variableRateData.getValue()); 1072 } 1073 catch (final IOException e) 1074 { 1075 debugException(e); 1076 err("Initializing the variable rates failed: " + e.getMessage()); 1077 return ResultCode.PARAM_ERROR; 1078 } 1079 catch (final IllegalArgumentException e) 1080 { 1081 debugException(e); 1082 err("Initializing the variable rates failed: " + e.getMessage()); 1083 return ResultCode.PARAM_ERROR; 1084 } 1085 } 1086 1087 1088 // Determine whether to include timestamps in the output and if so what 1089 // format should be used for them. 1090 final boolean includeTimestamp; 1091 final String timeFormat; 1092 if (timestampFormat.getValue().equalsIgnoreCase("with-date")) 1093 { 1094 includeTimestamp = true; 1095 timeFormat = "dd/MM/yyyy HH:mm:ss"; 1096 } 1097 else if (timestampFormat.getValue().equalsIgnoreCase("without-date")) 1098 { 1099 includeTimestamp = true; 1100 timeFormat = "HH:mm:ss"; 1101 } 1102 else 1103 { 1104 includeTimestamp = false; 1105 timeFormat = null; 1106 } 1107 1108 1109 // Determine whether any warm-up intervals should be run. 1110 final long totalIntervals; 1111 final boolean warmUp; 1112 int remainingWarmUpIntervals = warmUpIntervals.getValue(); 1113 if (remainingWarmUpIntervals > 0) 1114 { 1115 warmUp = true; 1116 totalIntervals = 0L + numIntervals.getValue() + remainingWarmUpIntervals; 1117 } 1118 else 1119 { 1120 warmUp = true; 1121 totalIntervals = 0L + numIntervals.getValue(); 1122 } 1123 1124 1125 // Create the table that will be used to format the output. 1126 final OutputFormat outputFormat; 1127 if (csvFormat.isPresent()) 1128 { 1129 outputFormat = OutputFormat.CSV; 1130 } 1131 else 1132 { 1133 outputFormat = OutputFormat.COLUMNS; 1134 } 1135 1136 final ColumnFormatter formatter = new ColumnFormatter(includeTimestamp, 1137 timeFormat, outputFormat, " ", 1138 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent", 1139 "Searches/Sec"), 1140 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent", 1141 "Srch Dur ms"), 1142 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent", 1143 "Mods/Sec"), 1144 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent", 1145 "Mod Dur ms"), 1146 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent", 1147 "Errors/Sec"), 1148 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall", 1149 "Searches/Sec"), 1150 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall", 1151 "Srch Dur ms"), 1152 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall", 1153 "Mods/Sec"), 1154 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall", 1155 "Mod Dur ms")); 1156 1157 1158 // Create values to use for statistics collection. 1159 final AtomicLong searchCounter = new AtomicLong(0L); 1160 final AtomicLong errorCounter = new AtomicLong(0L); 1161 final AtomicLong modCounter = new AtomicLong(0L); 1162 final AtomicLong modDurations = new AtomicLong(0L); 1163 final AtomicLong searchDurations = new AtomicLong(0L); 1164 final ResultCodeCounter rcCounter = new ResultCodeCounter(); 1165 1166 1167 // Determine the length of each interval in milliseconds. 1168 final long intervalMillis = 1000L * collectionInterval.getValue(); 1169 1170 1171 // Create the threads to use for the searches. 1172 final Random random = new Random(); 1173 final CyclicBarrier barrier = new CyclicBarrier(numThreads.getValue() + 1); 1174 final SearchAndModRateThread[] threads = 1175 new SearchAndModRateThread[numThreads.getValue()]; 1176 for (int i=0; i < threads.length; i++) 1177 { 1178 final LDAPConnection connection; 1179 try 1180 { 1181 connection = getConnection(); 1182 } 1183 catch (final LDAPException le) 1184 { 1185 debugException(le); 1186 err("Unable to connect to the directory server: ", 1187 getExceptionMessage(le)); 1188 return le.getResultCode(); 1189 } 1190 1191 threads[i] = new SearchAndModRateThread(this, i, connection, dnPattern, 1192 scopeArg.getValue(), filterPattern, returnAttrs, modAttrs, 1193 valueLength.getValue(), charSet, authzIDPattern, 1194 simplePageSize.getValue(), searchControls, modifyControls, 1195 iterationsBeforeReconnect.getValue(), random.nextLong(), barrier, 1196 searchCounter, modCounter, searchDurations, modDurations, 1197 errorCounter, rcCounter, fixedRateBarrier); 1198 threads[i].start(); 1199 } 1200 1201 1202 // Display the table header. 1203 for (final String headerLine : formatter.getHeaderLines(true)) 1204 { 1205 out(headerLine); 1206 } 1207 1208 1209 // Start the RateAdjustor before the threads so that the initial value is 1210 // in place before any load is generated unless we're doing a warm-up in 1211 // which case, we'll start it after the warm-up is complete. 1212 if ((rateAdjustor != null) && (remainingWarmUpIntervals <= 0)) 1213 { 1214 rateAdjustor.start(); 1215 } 1216 1217 1218 // Indicate that the threads can start running. 1219 try 1220 { 1221 barrier.await(); 1222 } 1223 catch (final Exception e) 1224 { 1225 debugException(e); 1226 } 1227 1228 long overallStartTime = System.nanoTime(); 1229 long nextIntervalStartTime = System.currentTimeMillis() + intervalMillis; 1230 1231 1232 boolean setOverallStartTime = false; 1233 long lastSearchDuration = 0L; 1234 long lastModDuration = 0L; 1235 long lastNumErrors = 0L; 1236 long lastNumSearches = 0L; 1237 long lastNumMods = 0L; 1238 long lastEndTime = System.nanoTime(); 1239 for (long i=0; i < totalIntervals; i++) 1240 { 1241 if (rateAdjustor != null) 1242 { 1243 if (! rateAdjustor.isAlive()) 1244 { 1245 out("All of the rates in " + variableRateData.getValue().getName() + 1246 " have been completed."); 1247 break; 1248 } 1249 } 1250 1251 final long startTimeMillis = System.currentTimeMillis(); 1252 final long sleepTimeMillis = nextIntervalStartTime - startTimeMillis; 1253 nextIntervalStartTime += intervalMillis; 1254 if (sleepTimeMillis > 0) 1255 { 1256 sleeper.sleep(sleepTimeMillis); 1257 } 1258 1259 if (stopRequested.get()) 1260 { 1261 break; 1262 } 1263 1264 final long endTime = System.nanoTime(); 1265 final long intervalDuration = endTime - lastEndTime; 1266 1267 final long numSearches; 1268 final long numMods; 1269 final long numErrors; 1270 final long totalSearchDuration; 1271 final long totalModDuration; 1272 if (warmUp && (remainingWarmUpIntervals > 0)) 1273 { 1274 numSearches = searchCounter.getAndSet(0L); 1275 numMods = modCounter.getAndSet(0L); 1276 numErrors = errorCounter.getAndSet(0L); 1277 totalSearchDuration = searchDurations.getAndSet(0L); 1278 totalModDuration = modDurations.getAndSet(0L); 1279 } 1280 else 1281 { 1282 numSearches = searchCounter.get(); 1283 numMods = modCounter.get(); 1284 numErrors = errorCounter.get(); 1285 totalSearchDuration = searchDurations.get(); 1286 totalModDuration = modDurations.get(); 1287 } 1288 1289 final long recentNumSearches = numSearches - lastNumSearches; 1290 final long recentNumMods = numMods - lastNumMods; 1291 final long recentNumErrors = numErrors - lastNumErrors; 1292 final long recentSearchDuration = 1293 totalSearchDuration - lastSearchDuration; 1294 final long recentModDuration = totalModDuration - lastModDuration; 1295 1296 final double numSeconds = intervalDuration / 1000000000.0d; 1297 final double recentSearchRate = recentNumSearches / numSeconds; 1298 final double recentModRate = recentNumMods / numSeconds; 1299 final double recentErrorRate = recentNumErrors / numSeconds; 1300 1301 final double recentAvgSearchDuration; 1302 if (recentNumSearches > 0L) 1303 { 1304 recentAvgSearchDuration = 1305 1.0d * recentSearchDuration / recentNumSearches / 1000000; 1306 } 1307 else 1308 { 1309 recentAvgSearchDuration = 0.0d; 1310 } 1311 1312 final double recentAvgModDuration; 1313 if (recentNumMods > 0L) 1314 { 1315 recentAvgModDuration = 1316 1.0d * recentModDuration / recentNumMods / 1000000; 1317 } 1318 else 1319 { 1320 recentAvgModDuration = 0.0d; 1321 } 1322 1323 if (warmUp && (remainingWarmUpIntervals > 0)) 1324 { 1325 out(formatter.formatRow(recentSearchRate, recentAvgSearchDuration, 1326 recentModRate, recentAvgModDuration, recentErrorRate, "warming up", 1327 "warming up", "warming up", "warming up")); 1328 1329 remainingWarmUpIntervals--; 1330 if (remainingWarmUpIntervals == 0) 1331 { 1332 out("Warm-up completed. Beginning overall statistics collection."); 1333 setOverallStartTime = true; 1334 if (rateAdjustor != null) 1335 { 1336 rateAdjustor.start(); 1337 } 1338 } 1339 } 1340 else 1341 { 1342 if (setOverallStartTime) 1343 { 1344 overallStartTime = lastEndTime; 1345 setOverallStartTime = false; 1346 } 1347 1348 final double numOverallSeconds = 1349 (endTime - overallStartTime) / 1000000000.0d; 1350 final double overallSearchRate = numSearches / numOverallSeconds; 1351 final double overallModRate = numMods / numOverallSeconds; 1352 1353 final double overallAvgSearchDuration; 1354 if (numSearches > 0L) 1355 { 1356 overallAvgSearchDuration = 1357 1.0d * totalSearchDuration / numSearches / 1000000; 1358 } 1359 else 1360 { 1361 overallAvgSearchDuration = 0.0d; 1362 } 1363 1364 final double overallAvgModDuration; 1365 if (numMods > 0L) 1366 { 1367 overallAvgModDuration = 1368 1.0d * totalModDuration / numMods / 1000000; 1369 } 1370 else 1371 { 1372 overallAvgModDuration = 0.0d; 1373 } 1374 1375 out(formatter.formatRow(recentSearchRate, recentAvgSearchDuration, 1376 recentModRate, recentAvgModDuration, recentErrorRate, 1377 overallSearchRate, overallAvgSearchDuration, overallModRate, 1378 overallAvgModDuration)); 1379 1380 lastNumSearches = numSearches; 1381 lastNumMods = numMods; 1382 lastNumErrors = numErrors; 1383 lastSearchDuration = totalSearchDuration; 1384 lastModDuration = totalModDuration; 1385 } 1386 1387 final List<ObjectPair<ResultCode,Long>> rcCounts = 1388 rcCounter.getCounts(true); 1389 if ((! suppressErrors.isPresent()) && (! rcCounts.isEmpty())) 1390 { 1391 err("\tError Results:"); 1392 for (final ObjectPair<ResultCode,Long> p : rcCounts) 1393 { 1394 err("\t", p.getFirst().getName(), ": ", p.getSecond()); 1395 } 1396 } 1397 1398 lastEndTime = endTime; 1399 } 1400 1401 1402 // Shut down the RateAdjustor if we have one. 1403 if (rateAdjustor != null) 1404 { 1405 rateAdjustor.shutDown(); 1406 } 1407 1408 // Stop all of the threads. 1409 ResultCode resultCode = ResultCode.SUCCESS; 1410 for (final SearchAndModRateThread t : threads) 1411 { 1412 final ResultCode r = t.stopRunning(); 1413 if (resultCode == ResultCode.SUCCESS) 1414 { 1415 resultCode = r; 1416 } 1417 } 1418 1419 return resultCode; 1420 } 1421 1422 1423 1424 /** 1425 * Requests that this tool stop running. This method will attempt to wait 1426 * for all threads to complete before returning control to the caller. 1427 */ 1428 public void stopRunning() 1429 { 1430 stopRequested.set(true); 1431 sleeper.wakeup(); 1432 1433 final Thread t = runningThread; 1434 if (t != null) 1435 { 1436 try 1437 { 1438 t.join(); 1439 } 1440 catch (final Exception e) 1441 { 1442 debugException(e); 1443 1444 if (e instanceof InterruptedException) 1445 { 1446 Thread.currentThread().interrupt(); 1447 } 1448 } 1449 } 1450 } 1451 1452 1453 1454 /** 1455 * {@inheritDoc} 1456 */ 1457 @Override() 1458 public LinkedHashMap<String[],String> getExampleUsages() 1459 { 1460 final LinkedHashMap<String[],String> examples = 1461 new LinkedHashMap<String[],String>(2); 1462 1463 String[] args = 1464 { 1465 "--hostname", "server.example.com", 1466 "--port", "389", 1467 "--bindDN", "uid=admin,dc=example,dc=com", 1468 "--bindPassword", "password", 1469 "--baseDN", "dc=example,dc=com", 1470 "--scope", "sub", 1471 "--filter", "(uid=user.[1-1000000])", 1472 "--attribute", "givenName", 1473 "--attribute", "sn", 1474 "--attribute", "mail", 1475 "--modifyAttribute", "description", 1476 "--valueLength", "10", 1477 "--characterSet", "abcdefghijklmnopqrstuvwxyz0123456789", 1478 "--numThreads", "10" 1479 }; 1480 String description = 1481 "Test search and modify performance by searching randomly across a " + 1482 "set of one million users located below 'dc=example,dc=com' with " + 1483 "ten concurrent threads. The entries returned to the client will " + 1484 "include the givenName, sn, and mail attributes, and the " + 1485 "description attribute of each entry returned will be replaced " + 1486 "with a string of ten randomly-selected alphanumeric characters."; 1487 examples.put(args, description); 1488 1489 args = new String[] 1490 { 1491 "--generateSampleRateFile", "variable-rate-data.txt" 1492 }; 1493 description = 1494 "Generate a sample variable rate definition file that may be used " + 1495 "in conjunction with the --variableRateData argument. The sample " + 1496 "file will include comments that describe the format for data to be " + 1497 "included in this file."; 1498 examples.put(args, description); 1499 1500 return examples; 1501 } 1502}