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