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