001/* 002 * Copyright 2008-2016 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2016 UnboundID Corp. 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.LinkedHashMap; 030import java.util.LinkedHashSet; 031import java.util.List; 032import java.util.Random; 033import java.util.concurrent.CyclicBarrier; 034import java.util.concurrent.atomic.AtomicBoolean; 035import java.util.concurrent.atomic.AtomicLong; 036 037import com.unboundid.ldap.sdk.LDAPConnection; 038import com.unboundid.ldap.sdk.LDAPConnectionOptions; 039import com.unboundid.ldap.sdk.LDAPException; 040import com.unboundid.ldap.sdk.ResultCode; 041import com.unboundid.ldap.sdk.Version; 042import com.unboundid.util.ColumnFormatter; 043import com.unboundid.util.FixedRateBarrier; 044import com.unboundid.util.FormattableColumn; 045import com.unboundid.util.HorizontalAlignment; 046import com.unboundid.util.LDAPCommandLineTool; 047import com.unboundid.util.ObjectPair; 048import com.unboundid.util.OutputFormat; 049import com.unboundid.util.RateAdjustor; 050import com.unboundid.util.ResultCodeCounter; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053import com.unboundid.util.ValuePattern; 054import com.unboundid.util.WakeableSleeper; 055import com.unboundid.util.args.ArgumentException; 056import com.unboundid.util.args.ArgumentParser; 057import com.unboundid.util.args.BooleanArgument; 058import com.unboundid.util.args.FileArgument; 059import com.unboundid.util.args.IntegerArgument; 060import com.unboundid.util.args.StringArgument; 061 062import static com.unboundid.util.Debug.*; 063import static com.unboundid.util.StaticUtils.*; 064 065 066 067/** 068 * This class provides a tool that can be used to perform repeated modifications 069 * in an LDAP directory server using multiple threads. It can help provide an 070 * estimate of the modify performance that a directory server is able to 071 * achieve. The target entry DN may be a value pattern as described in the 072 * {@link ValuePattern} class. This makes it possible to modify a range of 073 * entries rather than repeatedly updating the same entry. 074 * <BR><BR> 075 * Some of the APIs demonstrated by this example include: 076 * <UL> 077 * <LI>Argument Parsing (from the {@code com.unboundid.util.args} 078 * package)</LI> 079 * <LI>LDAP Command-Line Tool (from the {@code com.unboundid.util} 080 * package)</LI> 081 * <LI>LDAP Communication (from the {@code com.unboundid.ldap.sdk} 082 * package)</LI> 083 * <LI>Value Patterns (from the {@code com.unboundid.util} package)</LI> 084 * </UL> 085 * <BR><BR> 086 * All of the necessary information is provided using command line arguments. 087 * Supported arguments include those allowed by the {@link LDAPCommandLineTool} 088 * class, as well as the following additional arguments: 089 * <UL> 090 * <LI>"-b {entryDN}" or "--targetDN {baseDN}" -- specifies the DN of the 091 * entry to be modified. This must be provided. It may be a simple DN, 092 * or it may be a value pattern to express a range of entry DNs.</LI> 093 * <LI>"-A {name}" or "--attribute {name}" -- specifies the name of the 094 * attribute to modify. Multiple attributes may be modified by providing 095 * multiple instances of this argument. At least one attribute must be 096 * provided.</LI> 097 * <LI>"-l {num}" or "--valueLength {num}" -- specifies the length in bytes to 098 * use for the values of the target attributes. If this is not provided, 099 * then a default length of 10 bytes will be used.</LI> 100 * <LI>"-C {chars}" or "--characterSet {chars}" -- specifies the set of 101 * characters that will be used to generate the values to use for the 102 * target attributes. It should only include ASCII characters. Values 103 * will be generated from randomly-selected characters from this set. If 104 * this is not provided, then a default set of lowercase alphabetic 105 * characters will be used.</LI> 106 * <LI>"-t {num}" or "--numThreads {num}" -- specifies the number of 107 * concurrent threads to use when performing the modifications. If this 108 * is not provided, then a default of one thread will be used.</LI> 109 * <LI>"-i {sec}" or "--intervalDuration {sec}" -- specifies the length of 110 * time in seconds between lines out output. If this is not provided, 111 * then a default interval duration of five seconds will be used.</LI> 112 * <LI>"-I {num}" or "--numIntervals {num}" -- specifies the maximum number of 113 * intervals for which to run. If this is not provided, then it will 114 * run forever.</LI> 115 * <LI>"--iterationsBeforeReconnect {num}" -- specifies the number of modify 116 * iterations that should be performed on a connection before that 117 * connection is closed and replaced with a newly-established (and 118 * authenticated, if appropriate) connection.</LI> 119 * <LI>"-r {modifies-per-second}" or "--ratePerSecond {modifies-per-second}" 120 * -- specifies the target number of modifies to perform per second. It 121 * is still necessary to specify a sufficient number of threads for 122 * achieving this rate. If this option is not provided, then the tool 123 * will run at the maximum rate for the specified number of threads.</LI> 124 * <LI>"--variableRateData {path}" -- specifies the path to a file containing 125 * information needed to allow the tool to vary the target rate over time. 126 * If this option is not provided, then the tool will either use a fixed 127 * target rate as specified by the "--ratePerSecond" argument, or it will 128 * run at the maximum rate.</LI> 129 * <LI>"--generateSampleRateFile {path}" -- specifies the path to a file to 130 * which sample data will be written illustrating and describing the 131 * format of the file expected to be used in conjunction with the 132 * "--variableRateData" argument.</LI> 133 * <LI>"--warmUpIntervals {num}" -- specifies the number of intervals to 134 * complete before beginning overall statistics collection.</LI> 135 * <LI>"--timestampFormat {format}" -- specifies the format to use for 136 * timestamps included before each output line. The format may be one of 137 * "none" (for no timestamps), "with-date" (to include both the date and 138 * the time), or "without-date" (to include only time time).</LI> 139 * <LI>"-Y {authzID}" or "--proxyAs {authzID}" -- Use the proxied 140 * authorization v2 control to request that the operation be processed 141 * using an alternate authorization identity. In this case, the bind DN 142 * should be that of a user that has permission to use this control. The 143 * authorization identity may be a value pattern.</LI> 144 * <LI>"--suppressErrorResultCodes" -- Indicates that information about the 145 * result codes for failed operations should not be displayed.</LI> 146 * <LI>"-c" or "--csv" -- Generate output in CSV format rather than a 147 * display-friendly format.</LI> 148 * </UL> 149 */ 150@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 151public final class ModRate 152 extends LDAPCommandLineTool 153 implements Serializable 154{ 155 /** 156 * The serial version UID for this serializable class. 157 */ 158 private static final long serialVersionUID = 2709717414202815822L; 159 160 161 162 // Indicates whether a request has been made to stop running. 163 private final AtomicBoolean stopRequested; 164 165 // The argument used to indicate whether to generate output in CSV format. 166 private BooleanArgument csvFormat; 167 168 // The argument used to indicate whether to suppress information about error 169 // result codes. 170 private BooleanArgument suppressErrorsArgument; 171 172 // The argument used to specify the collection interval. 173 private IntegerArgument collectionInterval; 174 175 // The argument used to specify the number of modify iterations on a 176 // connection before it is closed and re-established. 177 private IntegerArgument iterationsBeforeReconnect; 178 179 // The argument used to specify the number of intervals. 180 private IntegerArgument numIntervals; 181 182 // The argument used to specify the number of threads. 183 private IntegerArgument numThreads; 184 185 // The argument used to specify the seed to use for the random number 186 // generator. 187 private IntegerArgument randomSeed; 188 189 // The target rate of modifies per second. 190 private IntegerArgument ratePerSecond; 191 192 // The argument used to specify a variable rate file. 193 private FileArgument sampleRateFile; 194 195 // The argument used to specify a variable rate file. 196 private FileArgument variableRateData; 197 198 // The argument used to specify the length of the values to generate. 199 private IntegerArgument valueLength; 200 201 // The number of warm-up intervals to perform. 202 private IntegerArgument warmUpIntervals; 203 204 // The argument used to specify the name of the attribute to modify. 205 private StringArgument attribute; 206 207 // The argument used to specify the set of characters to use when generating 208 // values. 209 private StringArgument characterSet; 210 211 // The argument used to specify the DNs of the entries to modify. 212 private StringArgument entryDN; 213 214 // The argument used to specify the proxied authorization identity. 215 private StringArgument proxyAs; 216 217 // The argument used to specify the timestamp format. 218 private StringArgument timestampFormat; 219 220 // The thread currently being used to run the searchrate tool. 221 private volatile Thread runningThread; 222 223 // A wakeable sleeper that will be used to sleep between reporting intervals. 224 private final WakeableSleeper sleeper; 225 226 227 228 /** 229 * Parse the provided command line arguments and make the appropriate set of 230 * changes. 231 * 232 * @param args The command line arguments provided to this program. 233 */ 234 public static void main(final String[] args) 235 { 236 final ResultCode resultCode = main(args, System.out, System.err); 237 if (resultCode != ResultCode.SUCCESS) 238 { 239 System.exit(resultCode.intValue()); 240 } 241 } 242 243 244 245 /** 246 * Parse the provided command line arguments and make the appropriate set of 247 * changes. 248 * 249 * @param args The command line arguments provided to this program. 250 * @param outStream The output stream to which standard out should be 251 * written. It may be {@code null} if output should be 252 * suppressed. 253 * @param errStream The output stream to which standard error should be 254 * written. It may be {@code null} if error messages 255 * should be suppressed. 256 * 257 * @return A result code indicating whether the processing was successful. 258 */ 259 public static ResultCode main(final String[] args, 260 final OutputStream outStream, 261 final OutputStream errStream) 262 { 263 final ModRate modRate = new ModRate(outStream, errStream); 264 return modRate.runTool(args); 265 } 266 267 268 269 /** 270 * Creates a new instance of this tool. 271 * 272 * @param outStream The output stream to which standard out should be 273 * written. It may be {@code null} if output should be 274 * suppressed. 275 * @param errStream The output stream to which standard error should be 276 * written. It may be {@code null} if error messages 277 * should be suppressed. 278 */ 279 public ModRate(final OutputStream outStream, final OutputStream errStream) 280 { 281 super(outStream, errStream); 282 283 stopRequested = new AtomicBoolean(false); 284 sleeper = new WakeableSleeper(); 285 } 286 287 288 289 /** 290 * Retrieves the name for this tool. 291 * 292 * @return The name for this tool. 293 */ 294 @Override() 295 public String getToolName() 296 { 297 return "modrate"; 298 } 299 300 301 302 /** 303 * Retrieves the description for this tool. 304 * 305 * @return The description for this tool. 306 */ 307 @Override() 308 public String getToolDescription() 309 { 310 return "Perform repeated modifications against " + 311 "an LDAP directory server."; 312 } 313 314 315 316 /** 317 * Retrieves the version string for this tool. 318 * 319 * @return The version string for this tool. 320 */ 321 @Override() 322 public String getToolVersion() 323 { 324 return Version.NUMERIC_VERSION_STRING; 325 } 326 327 328 329 /** 330 * Indicates whether this tool should provide support for an interactive mode, 331 * in which the tool offers a mode in which the arguments can be provided in 332 * a text-driven menu rather than requiring them to be given on the command 333 * line. If interactive mode is supported, it may be invoked using the 334 * "--interactive" argument. Alternately, if interactive mode is supported 335 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then 336 * interactive mode may be invoked by simply launching the tool without any 337 * arguments. 338 * 339 * @return {@code true} if this tool supports interactive mode, or 340 * {@code false} if not. 341 */ 342 @Override() 343 public boolean supportsInteractiveMode() 344 { 345 return true; 346 } 347 348 349 350 /** 351 * Indicates whether this tool defaults to launching in interactive mode if 352 * the tool is invoked without any command-line arguments. This will only be 353 * used if {@link #supportsInteractiveMode()} returns {@code true}. 354 * 355 * @return {@code true} if this tool defaults to using interactive mode if 356 * launched without any command-line arguments, or {@code false} if 357 * not. 358 */ 359 @Override() 360 public boolean defaultsToInteractiveMode() 361 { 362 return true; 363 } 364 365 366 367 /** 368 * Indicates whether this tool supports the use of a properties file for 369 * specifying default values for arguments that aren't specified on the 370 * command line. 371 * 372 * @return {@code true} if this tool supports the use of a properties file 373 * for specifying default values for arguments that aren't specified 374 * on the command line, or {@code false} if not. 375 */ 376 @Override() 377 public boolean supportsPropertiesFile() 378 { 379 return true; 380 } 381 382 383 384 /** 385 * Indicates whether the LDAP-specific arguments should include alternate 386 * versions of all long identifiers that consist of multiple words so that 387 * they are available in both camelCase and dash-separated versions. 388 * 389 * @return {@code true} if this tool should provide multiple versions of 390 * long identifiers for LDAP-specific arguments, or {@code false} if 391 * not. 392 */ 393 @Override() 394 protected boolean includeAlternateLongIdentifiers() 395 { 396 return true; 397 } 398 399 400 401 /** 402 * Adds the arguments used by this program that aren't already provided by the 403 * generic {@code LDAPCommandLineTool} framework. 404 * 405 * @param parser The argument parser to which the arguments should be added. 406 * 407 * @throws ArgumentException If a problem occurs while adding the arguments. 408 */ 409 @Override() 410 public void addNonLDAPArguments(final ArgumentParser parser) 411 throws ArgumentException 412 { 413 String description = "The DN of the entry to modify. It may be a simple " + 414 "DN or a value pattern to specify a range of DN (e.g., " + 415 "\"uid=user.[1-1000],ou=People,dc=example,dc=com\"). See " + 416 ValuePattern.PUBLIC_JAVADOC_URL + " for complete details about the " + 417 "value pattern syntax. This must be provided."; 418 entryDN = new StringArgument('b', "entryDN", true, 1, "{dn}", description); 419 entryDN.setArgumentGroupName("Modification Arguments"); 420 entryDN.addLongIdentifier("entry-dn"); 421 parser.addArgument(entryDN); 422 423 424 description = "The name of the attribute to modify. Multiple attributes " + 425 "may be specified by providing this argument multiple " + 426 "times. At least one attribute must be specified."; 427 attribute = new StringArgument('A', "attribute", true, 0, "{name}", 428 description); 429 attribute.setArgumentGroupName("Modification Arguments"); 430 parser.addArgument(attribute); 431 432 433 description = "The length in bytes to use when generating values for the " + 434 "modifications. If this is not provided, then a default " + 435 "length of ten bytes will be used."; 436 valueLength = new IntegerArgument('l', "valueLength", true, 1, "{num}", 437 description, 1, Integer.MAX_VALUE, 10); 438 valueLength.setArgumentGroupName("Modification Arguments"); 439 valueLength.addLongIdentifier("value-length"); 440 parser.addArgument(valueLength); 441 442 443 description = "The set of characters to use to generate the values for " + 444 "the modifications. It should only include ASCII " + 445 "characters. If this is not provided, then a default set " + 446 "of lowercase alphabetic characters will be used."; 447 characterSet = new StringArgument('C', "characterSet", true, 1, "{chars}", 448 description, 449 "abcdefghijklmnopqrstuvwxyz"); 450 characterSet.setArgumentGroupName("Modification Arguments"); 451 characterSet.addLongIdentifier("character-set"); 452 parser.addArgument(characterSet); 453 454 455 description = "The number of threads to use to perform the " + 456 "modifications. If this is not provided, a single thread " + 457 "will be used."; 458 numThreads = new IntegerArgument('t', "numThreads", true, 1, "{num}", 459 description, 1, Integer.MAX_VALUE, 1); 460 numThreads.setArgumentGroupName("Rate Management Arguments"); 461 numThreads.addLongIdentifier("num-threads"); 462 parser.addArgument(numThreads); 463 464 465 description = "The length of time in seconds between output lines. If " + 466 "this is not provided, then a default interval of five " + 467 "seconds will be used."; 468 collectionInterval = new IntegerArgument('i', "intervalDuration", true, 1, 469 "{num}", description, 1, 470 Integer.MAX_VALUE, 5); 471 collectionInterval.setArgumentGroupName("Rate Management Arguments"); 472 collectionInterval.addLongIdentifier("interval-duration"); 473 parser.addArgument(collectionInterval); 474 475 476 description = "The maximum number of intervals for which to run. If " + 477 "this is not provided, then the tool will run until it is " + 478 "interrupted."; 479 numIntervals = new IntegerArgument('I', "numIntervals", true, 1, "{num}", 480 description, 1, Integer.MAX_VALUE, 481 Integer.MAX_VALUE); 482 numIntervals.setArgumentGroupName("Rate Management Arguments"); 483 numIntervals.addLongIdentifier("num-intervals"); 484 parser.addArgument(numIntervals); 485 486 description = "The number of modify iterations that should be processed " + 487 "on a connection before that connection is closed and " + 488 "replaced with a newly-established (and authenticated, if " + 489 "appropriate) connection. If this is not provided, then " + 490 "connections will not be periodically closed and " + 491 "re-established."; 492 iterationsBeforeReconnect = new IntegerArgument(null, 493 "iterationsBeforeReconnect", false, 1, "{num}", description, 0); 494 iterationsBeforeReconnect.setArgumentGroupName("Rate Management Arguments"); 495 iterationsBeforeReconnect.addLongIdentifier("iterations-before-reconnect"); 496 parser.addArgument(iterationsBeforeReconnect); 497 498 description = "The target number of modifies to perform per second. It " + 499 "is still necessary to specify a sufficient number of " + 500 "threads for achieving this rate. If neither this option " + 501 "nor --variableRateData is provided, then the tool will " + 502 "run at the maximum rate for the specified number of " + 503 "threads."; 504 ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1, 505 "{modifies-per-second}", description, 506 1, Integer.MAX_VALUE); 507 ratePerSecond.setArgumentGroupName("Rate Management Arguments"); 508 ratePerSecond.addLongIdentifier("rate-per-second"); 509 parser.addArgument(ratePerSecond); 510 511 final String variableRateDataArgName = "variableRateData"; 512 final String generateSampleRateFileArgName = "generateSampleRateFile"; 513 description = RateAdjustor.getVariableRateDataArgumentDescription( 514 generateSampleRateFileArgName); 515 variableRateData = new FileArgument(null, variableRateDataArgName, false, 1, 516 "{path}", description, true, true, true, 517 false); 518 variableRateData.setArgumentGroupName("Rate Management Arguments"); 519 variableRateData.addLongIdentifier("variable-rate-data"); 520 parser.addArgument(variableRateData); 521 522 description = RateAdjustor.getGenerateSampleVariableRateFileDescription( 523 variableRateDataArgName); 524 sampleRateFile = new FileArgument(null, generateSampleRateFileArgName, 525 false, 1, "{path}", description, false, 526 true, true, false); 527 sampleRateFile.setArgumentGroupName("Rate Management Arguments"); 528 sampleRateFile.addLongIdentifier("generate-sample-rate-file"); 529 sampleRateFile.setUsageArgument(true); 530 parser.addArgument(sampleRateFile); 531 parser.addExclusiveArgumentSet(variableRateData, sampleRateFile); 532 533 description = "The number of intervals to complete before beginning " + 534 "overall statistics collection. Specifying a nonzero " + 535 "number of warm-up intervals gives the client and server " + 536 "a chance to warm up without skewing performance results."; 537 warmUpIntervals = new IntegerArgument(null, "warmUpIntervals", true, 1, 538 "{num}", description, 0, Integer.MAX_VALUE, 0); 539 warmUpIntervals.setArgumentGroupName("Rate Management Arguments"); 540 warmUpIntervals.addLongIdentifier("warm-up-intervals"); 541 parser.addArgument(warmUpIntervals); 542 543 description = "Indicates the format to use for timestamps included in " + 544 "the output. A value of 'none' indicates that no " + 545 "timestamps should be included. A value of 'with-date' " + 546 "indicates that both the date and the time should be " + 547 "included. A value of 'without-date' indicates that only " + 548 "the time should be included."; 549 final LinkedHashSet<String> allowedFormats = new LinkedHashSet<String>(3); 550 allowedFormats.add("none"); 551 allowedFormats.add("with-date"); 552 allowedFormats.add("without-date"); 553 timestampFormat = new StringArgument(null, "timestampFormat", true, 1, 554 "{format}", description, allowedFormats, "none"); 555 timestampFormat.addLongIdentifier("timestamp-format"); 556 parser.addArgument(timestampFormat); 557 558 description = "Indicates that the proxied authorization control (as " + 559 "defined in RFC 4370) should be used to request that " + 560 "operations be processed using an alternate authorization " + 561 "identity. This may be a simple authorization ID or it " + 562 "may be a value pattern to specify a range of " + 563 "identities. See " + ValuePattern.PUBLIC_JAVADOC_URL + 564 " for complete details about the value pattern syntax."; 565 proxyAs = new StringArgument('Y', "proxyAs", false, 1, "{authzID}", 566 description); 567 proxyAs.addLongIdentifier("proxy-as"); 568 parser.addArgument(proxyAs); 569 570 description = "Indicates that information about the result codes for " + 571 "failed operations should not be displayed."; 572 suppressErrorsArgument = new BooleanArgument(null, 573 "suppressErrorResultCodes", 1, description); 574 suppressErrorsArgument.addLongIdentifier("suppress-error-result-codes"); 575 parser.addArgument(suppressErrorsArgument); 576 577 description = "Generate output in CSV format rather than a " + 578 "display-friendly format"; 579 csvFormat = new BooleanArgument('c', "csv", 1, description); 580 parser.addArgument(csvFormat); 581 582 description = "Specifies the seed to use for the random number generator."; 583 randomSeed = new IntegerArgument('R', "randomSeed", false, 1, "{value}", 584 description); 585 randomSeed.addLongIdentifier("random-seed"); 586 parser.addArgument(randomSeed); 587 } 588 589 590 591 /** 592 * Indicates whether this tool supports creating connections to multiple 593 * servers. If it is to support multiple servers, then the "--hostname" and 594 * "--port" arguments will be allowed to be provided multiple times, and 595 * will be required to be provided the same number of times. The same type of 596 * communication security and bind credentials will be used for all servers. 597 * 598 * @return {@code true} if this tool supports creating connections to 599 * multiple servers, or {@code false} if not. 600 */ 601 @Override() 602 protected boolean supportsMultipleServers() 603 { 604 return true; 605 } 606 607 608 609 /** 610 * Retrieves the connection options that should be used for connections 611 * created for use with this tool. 612 * 613 * @return The connection options that should be used for connections created 614 * for use with this tool. 615 */ 616 @Override() 617 public LDAPConnectionOptions getConnectionOptions() 618 { 619 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 620 options.setUseSynchronousMode(true); 621 return options; 622 } 623 624 625 626 /** 627 * Performs the actual processing for this tool. In this case, it gets a 628 * connection to the directory server and uses it to perform the requested 629 * modifications. 630 * 631 * @return The result code for the processing that was performed. 632 */ 633 @Override() 634 public ResultCode doToolProcessing() 635 { 636 runningThread = Thread.currentThread(); 637 638 try 639 { 640 return doToolProcessingInternal(); 641 } 642 finally 643 { 644 runningThread = null; 645 } 646 647 } 648 649 650 /** 651 * Performs the actual processing for this tool. In this case, it gets a 652 * connection to the directory server and uses it to perform the requested 653 * modifications. 654 * 655 * @return The result code for the processing that was performed. 656 */ 657 private ResultCode doToolProcessingInternal() 658 { 659 // If the sample rate file argument was specified, then generate the sample 660 // variable rate data file and return. 661 if (sampleRateFile.isPresent()) 662 { 663 try 664 { 665 RateAdjustor.writeSampleVariableRateFile(sampleRateFile.getValue()); 666 return ResultCode.SUCCESS; 667 } 668 catch (final Exception e) 669 { 670 debugException(e); 671 err("An error occurred while trying to write sample variable data " + 672 "rate file '", sampleRateFile.getValue().getAbsolutePath(), 673 "': ", getExceptionMessage(e)); 674 return ResultCode.LOCAL_ERROR; 675 } 676 } 677 678 679 // Determine the random seed to use. 680 final Long seed; 681 if (randomSeed.isPresent()) 682 { 683 seed = Long.valueOf(randomSeed.getValue()); 684 } 685 else 686 { 687 seed = null; 688 } 689 690 // Create the value patterns for the target entry DN and proxied 691 // authorization identities. 692 final ValuePattern dnPattern; 693 try 694 { 695 dnPattern = new ValuePattern(entryDN.getValue(), seed); 696 } 697 catch (final ParseException pe) 698 { 699 debugException(pe); 700 err("Unable to parse the entry DN value pattern: ", pe.getMessage()); 701 return ResultCode.PARAM_ERROR; 702 } 703 704 final ValuePattern authzIDPattern; 705 if (proxyAs.isPresent()) 706 { 707 try 708 { 709 authzIDPattern = new ValuePattern(proxyAs.getValue(), seed); 710 } 711 catch (final ParseException pe) 712 { 713 debugException(pe); 714 err("Unable to parse the proxied authorization pattern: ", 715 pe.getMessage()); 716 return ResultCode.PARAM_ERROR; 717 } 718 } 719 else 720 { 721 authzIDPattern = null; 722 } 723 724 725 // Get the names of the attributes to modify. 726 final String[] attrs = new String[attribute.getValues().size()]; 727 attribute.getValues().toArray(attrs); 728 729 730 // Get the character set as a byte array. 731 final byte[] charSet = getBytes(characterSet.getValue()); 732 733 734 // If the --ratePerSecond option was specified, then limit the rate 735 // accordingly. 736 FixedRateBarrier fixedRateBarrier = null; 737 if (ratePerSecond.isPresent() || variableRateData.isPresent()) 738 { 739 // We might not have a rate per second if --variableRateData is specified. 740 // The rate typically doesn't matter except when we have warm-up 741 // intervals. In this case, we'll run at the max rate. 742 final int intervalSeconds = collectionInterval.getValue(); 743 final int ratePerInterval = 744 (ratePerSecond.getValue() == null) 745 ? Integer.MAX_VALUE 746 : ratePerSecond.getValue() * intervalSeconds; 747 fixedRateBarrier = 748 new FixedRateBarrier(1000L * intervalSeconds, ratePerInterval); 749 } 750 751 752 // If --variableRateData was specified, then initialize a RateAdjustor. 753 RateAdjustor rateAdjustor = null; 754 if (variableRateData.isPresent()) 755 { 756 try 757 { 758 rateAdjustor = RateAdjustor.newInstance(fixedRateBarrier, 759 ratePerSecond.getValue(), variableRateData.getValue()); 760 } 761 catch (final IOException e) 762 { 763 debugException(e); 764 err("Initializing the variable rates failed: " + e.getMessage()); 765 return ResultCode.PARAM_ERROR; 766 } 767 catch (final IllegalArgumentException e) 768 { 769 debugException(e); 770 err("Initializing the variable rates failed: " + e.getMessage()); 771 return ResultCode.PARAM_ERROR; 772 } 773 } 774 775 776 // Determine whether to include timestamps in the output and if so what 777 // format should be used for them. 778 final boolean includeTimestamp; 779 final String timeFormat; 780 if (timestampFormat.getValue().equalsIgnoreCase("with-date")) 781 { 782 includeTimestamp = true; 783 timeFormat = "dd/MM/yyyy HH:mm:ss"; 784 } 785 else if (timestampFormat.getValue().equalsIgnoreCase("without-date")) 786 { 787 includeTimestamp = true; 788 timeFormat = "HH:mm:ss"; 789 } 790 else 791 { 792 includeTimestamp = false; 793 timeFormat = null; 794 } 795 796 797 // Determine whether any warm-up intervals should be run. 798 final long totalIntervals; 799 final boolean warmUp; 800 int remainingWarmUpIntervals = warmUpIntervals.getValue(); 801 if (remainingWarmUpIntervals > 0) 802 { 803 warmUp = true; 804 totalIntervals = 0L + numIntervals.getValue() + remainingWarmUpIntervals; 805 } 806 else 807 { 808 warmUp = true; 809 totalIntervals = 0L + numIntervals.getValue(); 810 } 811 812 813 // Create the table that will be used to format the output. 814 final OutputFormat outputFormat; 815 if (csvFormat.isPresent()) 816 { 817 outputFormat = OutputFormat.CSV; 818 } 819 else 820 { 821 outputFormat = OutputFormat.COLUMNS; 822 } 823 824 final ColumnFormatter formatter = new ColumnFormatter(includeTimestamp, 825 timeFormat, outputFormat, " ", 826 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent", 827 "Mods/Sec"), 828 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent", 829 "Avg Dur ms"), 830 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent", 831 "Errors/Sec"), 832 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall", 833 "Mods/Sec"), 834 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall", 835 "Avg Dur ms")); 836 837 838 // Create values to use for statistics collection. 839 final AtomicLong modCounter = new AtomicLong(0L); 840 final AtomicLong errorCounter = new AtomicLong(0L); 841 final AtomicLong modDurations = new AtomicLong(0L); 842 final ResultCodeCounter rcCounter = new ResultCodeCounter(); 843 844 845 // Determine the length of each interval in milliseconds. 846 final long intervalMillis = 1000L * collectionInterval.getValue(); 847 848 849 // Create a random number generator to use for seeding the per-thread 850 // generators. 851 final Random random = new Random(); 852 853 854 // Create the threads to use for the modifications. 855 final CyclicBarrier barrier = new CyclicBarrier(numThreads.getValue() + 1); 856 final ModRateThread[] threads = new ModRateThread[numThreads.getValue()]; 857 for (int i=0; i < threads.length; i++) 858 { 859 final LDAPConnection connection; 860 try 861 { 862 connection = getConnection(); 863 } 864 catch (final LDAPException le) 865 { 866 debugException(le); 867 err("Unable to connect to the directory server: ", 868 getExceptionMessage(le)); 869 return le.getResultCode(); 870 } 871 872 threads[i] = new ModRateThread(this, i, connection, dnPattern, attrs, 873 charSet, valueLength.getValue(), authzIDPattern, random.nextLong(), 874 iterationsBeforeReconnect.getValue(), barrier, modCounter, 875 modDurations, errorCounter, rcCounter, fixedRateBarrier); 876 threads[i].start(); 877 } 878 879 880 // Display the table header. 881 for (final String headerLine : formatter.getHeaderLines(true)) 882 { 883 out(headerLine); 884 } 885 886 887 // Start the RateAdjustor before the threads so that the initial value is 888 // in place before any load is generated unless we're doing a warm-up in 889 // which case, we'll start it after the warm-up is complete. 890 if ((rateAdjustor != null) && (remainingWarmUpIntervals <= 0)) 891 { 892 rateAdjustor.start(); 893 } 894 895 896 // Indicate that the threads can start running. 897 try 898 { 899 barrier.await(); 900 } 901 catch (final Exception e) 902 { 903 debugException(e); 904 } 905 906 long overallStartTime = System.nanoTime(); 907 long nextIntervalStartTime = System.currentTimeMillis() + intervalMillis; 908 909 910 boolean setOverallStartTime = false; 911 long lastDuration = 0L; 912 long lastNumErrors = 0L; 913 long lastNumMods = 0L; 914 long lastEndTime = System.nanoTime(); 915 for (long i=0; i < totalIntervals; i++) 916 { 917 if (rateAdjustor != null) 918 { 919 if (! rateAdjustor.isAlive()) 920 { 921 out("All of the rates in " + variableRateData.getValue().getName() + 922 " have been completed."); 923 break; 924 } 925 } 926 927 final long startTimeMillis = System.currentTimeMillis(); 928 final long sleepTimeMillis = nextIntervalStartTime - startTimeMillis; 929 nextIntervalStartTime += intervalMillis; 930 if (sleepTimeMillis > 0) 931 { 932 sleeper.sleep(sleepTimeMillis); 933 } 934 935 if (stopRequested.get()) 936 { 937 break; 938 } 939 940 final long endTime = System.nanoTime(); 941 final long intervalDuration = endTime - lastEndTime; 942 943 final long numMods; 944 final long numErrors; 945 final long totalDuration; 946 if (warmUp && (remainingWarmUpIntervals > 0)) 947 { 948 numMods = modCounter.getAndSet(0L); 949 numErrors = errorCounter.getAndSet(0L); 950 totalDuration = modDurations.getAndSet(0L); 951 } 952 else 953 { 954 numMods = modCounter.get(); 955 numErrors = errorCounter.get(); 956 totalDuration = modDurations.get(); 957 } 958 959 final long recentNumMods = numMods - lastNumMods; 960 final long recentNumErrors = numErrors - lastNumErrors; 961 final long recentDuration = totalDuration - lastDuration; 962 963 final double numSeconds = intervalDuration / 1000000000.0d; 964 final double recentModRate = recentNumMods / numSeconds; 965 final double recentErrorRate = recentNumErrors / numSeconds; 966 967 final double recentAvgDuration; 968 if (recentNumMods > 0L) 969 { 970 recentAvgDuration = 1.0d * recentDuration / recentNumMods / 1000000; 971 } 972 else 973 { 974 recentAvgDuration = 0.0d; 975 } 976 977 if (warmUp && (remainingWarmUpIntervals > 0)) 978 { 979 out(formatter.formatRow(recentModRate, recentAvgDuration, 980 recentErrorRate, "warming up", "warming up")); 981 982 remainingWarmUpIntervals--; 983 if (remainingWarmUpIntervals == 0) 984 { 985 out("Warm-up completed. Beginning overall statistics collection."); 986 setOverallStartTime = true; 987 if (rateAdjustor != null) 988 { 989 rateAdjustor.start(); 990 } 991 } 992 } 993 else 994 { 995 if (setOverallStartTime) 996 { 997 overallStartTime = lastEndTime; 998 setOverallStartTime = false; 999 } 1000 1001 final double numOverallSeconds = 1002 (endTime - overallStartTime) / 1000000000.0d; 1003 final double overallAuthRate = numMods / numOverallSeconds; 1004 1005 final double overallAvgDuration; 1006 if (numMods > 0L) 1007 { 1008 overallAvgDuration = 1.0d * totalDuration / numMods / 1000000; 1009 } 1010 else 1011 { 1012 overallAvgDuration = 0.0d; 1013 } 1014 1015 out(formatter.formatRow(recentModRate, recentAvgDuration, 1016 recentErrorRate, overallAuthRate, overallAvgDuration)); 1017 1018 lastNumMods = numMods; 1019 lastNumErrors = numErrors; 1020 lastDuration = totalDuration; 1021 } 1022 1023 final List<ObjectPair<ResultCode,Long>> rcCounts = 1024 rcCounter.getCounts(true); 1025 if ((! suppressErrorsArgument.isPresent()) && (! rcCounts.isEmpty())) 1026 { 1027 err("\tError Results:"); 1028 for (final ObjectPair<ResultCode,Long> p : rcCounts) 1029 { 1030 err("\t", p.getFirst().getName(), ": ", p.getSecond()); 1031 } 1032 } 1033 1034 lastEndTime = endTime; 1035 } 1036 1037 // Shut down the RateAdjustor if we have one. 1038 if (rateAdjustor != null) 1039 { 1040 rateAdjustor.shutDown(); 1041 } 1042 1043 // Stop all of the threads. 1044 ResultCode resultCode = ResultCode.SUCCESS; 1045 for (final ModRateThread t : threads) 1046 { 1047 final ResultCode r = t.stopRunning(); 1048 if (resultCode == ResultCode.SUCCESS) 1049 { 1050 resultCode = r; 1051 } 1052 } 1053 1054 return resultCode; 1055 } 1056 1057 1058 1059 /** 1060 * Requests that this tool stop running. This method will attempt to wait 1061 * for all threads to complete before returning control to the caller. 1062 */ 1063 public void stopRunning() 1064 { 1065 stopRequested.set(true); 1066 sleeper.wakeup(); 1067 1068 final Thread t = runningThread; 1069 if (t != null) 1070 { 1071 try 1072 { 1073 t.join(); 1074 } 1075 catch (final Exception e) 1076 { 1077 debugException(e); 1078 } 1079 } 1080 } 1081 1082 1083 1084 /** 1085 * {@inheritDoc} 1086 */ 1087 @Override() 1088 public LinkedHashMap<String[],String> getExampleUsages() 1089 { 1090 final LinkedHashMap<String[],String> examples = 1091 new LinkedHashMap<String[],String>(2); 1092 1093 String[] args = 1094 { 1095 "--hostname", "server.example.com", 1096 "--port", "389", 1097 "--bindDN", "uid=admin,dc=example,dc=com", 1098 "--bindPassword", "password", 1099 "--entryDN", "uid=user.[1-1000000],ou=People,dc=example,dc=com", 1100 "--attribute", "description", 1101 "--valueLength", "12", 1102 "--numThreads", "10" 1103 }; 1104 String description = 1105 "Test modify performance by randomly selecting entries across a set " + 1106 "of one million users located below 'ou=People,dc=example,dc=com' " + 1107 "with ten concurrent threads and replacing the values for the " + 1108 "description attribute with a string of 12 randomly-selected " + 1109 "lowercase alphabetic characters."; 1110 examples.put(args, description); 1111 1112 args = new String[] 1113 { 1114 "--generateSampleRateFile", "variable-rate-data.txt" 1115 }; 1116 description = 1117 "Generate a sample variable rate definition file that may be used " + 1118 "in conjunction with the --variableRateData argument. The sample " + 1119 "file will include comments that describe the format for data to be " + 1120 "included in this file."; 1121 examples.put(args, description); 1122 1123 return examples; 1124 } 1125}