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