001/* 002 * Copyright 2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2020 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) 2020 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.ByteArrayOutputStream; 041import java.io.File; 042import java.io.OutputStream; 043import java.util.ArrayList; 044import java.util.Collections; 045import java.util.LinkedHashMap; 046import java.util.List; 047import java.util.concurrent.atomic.AtomicReference; 048import javax.net.ServerSocketFactory; 049 050import com.unboundid.ldap.listener.CannedResponseRequestHandler; 051import com.unboundid.ldap.listener.LDAPListener; 052import com.unboundid.ldap.listener.LDAPListenerConfig; 053import com.unboundid.ldap.sdk.Attribute; 054import com.unboundid.ldap.sdk.Entry; 055import com.unboundid.ldap.sdk.ResultCode; 056import com.unboundid.ldap.sdk.SearchResultReference; 057import com.unboundid.ldap.sdk.Version; 058import com.unboundid.util.CommandLineTool; 059import com.unboundid.util.Debug; 060import com.unboundid.util.NotNull; 061import com.unboundid.util.Nullable; 062import com.unboundid.util.StaticUtils; 063import com.unboundid.util.ThreadSafety; 064import com.unboundid.util.ThreadSafetyLevel; 065import com.unboundid.util.args.ArgumentException; 066import com.unboundid.util.args.ArgumentParser; 067import com.unboundid.util.args.BooleanArgument; 068import com.unboundid.util.args.IntegerArgument; 069import com.unboundid.util.args.StringArgument; 070import com.unboundid.util.ssl.KeyStoreKeyManager; 071import com.unboundid.util.ssl.SSLUtil; 072import com.unboundid.util.ssl.TrustAllTrustManager; 073import com.unboundid.util.ssl.cert.ManageCertificates; 074 075 076 077/** 078 * This class implements a command-line tool that can be helpful in measuring 079 * the performance of the LDAP SDK itself. It creates an {@link LDAPListener} 080 * that uses a {@link CannedResponseRequestHandler} to return a predefined 081 * response to any request that it receives. It will then use one of the 082 * {@link SearchRate}, {@link ModRate}, {@link AuthRate}, or 083 * {@link SearchAndModRate} tools to issue concurrent operations against that 084 * listener instance as quickly as possible. 085 */ 086@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 087public final class TestLDAPSDKPerformance 088 extends CommandLineTool 089{ 090 /** 091 * The column at which to wrap long lines. 092 */ 093 private static final int WRAP_COLUMN = 094 StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 095 096 097 098 /** 099 * The name for the authrate tool. 100 */ 101 @NotNull private static final String TOOL_NAME_AUTHRATE = "authrate"; 102 103 104 105 /** 106 * The name for the modrate tool. 107 */ 108 @NotNull private static final String TOOL_NAME_MODRATE = "modrate"; 109 110 111 112 /** 113 * The name for the search-and-mod-rate tool. 114 */ 115 @NotNull private static final String TOOL_NAME_SEARCH_AND_MOD_RATE = 116 "search-and-mod-rate"; 117 118 119 120 /** 121 * The name for the searchrate tool. 122 */ 123 @NotNull private static final String TOOL_NAME_SEARCHRATE = "searchrate"; 124 125 126 127 // A reference to the completion message for the tool. 128 @NotNull private final AtomicReference<String> completionMessage; 129 130 // The argument used to indicate that the authrate tool should only perform 131 // binds rather than both binds and searches. 132 @Nullable private BooleanArgument bindOnlyArg; 133 134 // The argument used to indicate whether to communicate with the listener 135 // over an SSL-encrypted connection. 136 @Nullable private BooleanArgument useSSLArg; 137 138 // The argument used to specify the number of entries to return in response to 139 // each search. 140 @Nullable private IntegerArgument entriesPerSearchArg; 141 142 // The argument used to specify the duration (in seconds) to use for each 143 // interval. 144 @Nullable private IntegerArgument intervalDurationSecondsArg; 145 146 // The argument used to specify the number of intervals to complete. 147 @Nullable private IntegerArgument numIntervalsArg; 148 149 // The argument used to specify the number of concurrent threads to use when 150 // searching. 151 @Nullable private IntegerArgument numThreadsArg; 152 153 // The argument used to specify the result code to return in response to each 154 // operation. 155 @Nullable private IntegerArgument resultCodeArg; 156 157 // The argument used to specify the number of warm-up intervals to use whose 158 // performance will be ignored in the final results. 159 @Nullable private IntegerArgument warmUpIntervalsArg; 160 161 // The argument used to specify the diagnostic message to include in each 162 // search result done message. 163 @Nullable private StringArgument diagnosticMessageArg; 164 165 // The argument used to specify the name of the tool to invoke for the 166 // performance testing. 167 @Nullable private StringArgument toolArg; 168 169 170 171 /** 172 * Runs this tool with the provided set of command-line arguments. 173 * 174 * @param args The command-line arguments provided to this program. 175 */ 176 public static void main(@NotNull final String... args) 177 { 178 final ResultCode resultCode = main(System.out, System.err, args); 179 if (resultCode != ResultCode.SUCCESS) 180 { 181 System.exit(resultCode.intValue()); 182 } 183 } 184 185 186 187 /** 188 * Runs this tool with the provided set of command-line arguments. 189 * 190 * @param out The output stream to use for standard output. It may be 191 * {@code null} if standard output should be suppressed. 192 * @param err The output stream to use for standard error. It may be 193 * {@code null} if standard error should be suppressed. 194 * @param args The command-line arguments provided to this program. 195 * 196 * @return A result code indicating the result of tool processing. Any 197 * result code other than {@link ResultCode#SUCCESS} should be 198 * considered an error. 199 */ 200 @NotNull() 201 public static ResultCode main(@Nullable final OutputStream out, 202 @Nullable final OutputStream err, 203 @NotNull final String... args) 204 { 205 final TestLDAPSDKPerformance tool = new TestLDAPSDKPerformance(out, err); 206 return tool.runTool(args); 207 } 208 209 210 211 /** 212 * Creates a new instance of this command-line tool. 213 * 214 * @param out The output stream to use for standard output. It may be 215 * {@code null} if standard output should be suppressed. 216 * @param err The output stream to use for standard error. It may be 217 * {@code null} if standard error should be suppressed. 218 */ 219 public TestLDAPSDKPerformance(@Nullable final OutputStream out, 220 @Nullable final OutputStream err) 221 { 222 super(out, err); 223 224 completionMessage = new AtomicReference<>(); 225 226 bindOnlyArg = null; 227 useSSLArg = null; 228 entriesPerSearchArg = null; 229 intervalDurationSecondsArg = null; 230 numIntervalsArg = null; 231 numThreadsArg = null; 232 resultCodeArg = null; 233 warmUpIntervalsArg = null; 234 diagnosticMessageArg = null; 235 toolArg = null; 236 } 237 238 239 240 /** 241 * Retrieves the name of this tool. It should be the name of the command used 242 * to invoke this tool. 243 * 244 * @return The name for this tool. 245 */ 246 @Override() 247 @NotNull() 248 public String getToolName() 249 { 250 return "test-ldap-sdk-performance"; 251 } 252 253 254 255 /** 256 * Retrieves a human-readable description for this tool. If the description 257 * should include multiple paragraphs, then this method should return the text 258 * for the first paragraph, and the 259 * {@link #getAdditionalDescriptionParagraphs()} method should be used to 260 * return the text for the subsequent paragraphs. 261 * 262 * @return A human-readable description for this tool. 263 */ 264 @Override() 265 @NotNull() 266 public String getToolDescription() 267 { 268 return "Provides a mechanism to help test the performance of the LDAP SDK."; 269 } 270 271 272 273 /** 274 * Retrieves additional paragraphs that should be included in the description 275 * for this tool. If the tool description should include multiple paragraphs, 276 * then the {@link #getToolDescription()} method should return the text of the 277 * first paragraph, and each item in the list returned by this method should 278 * be the text for each subsequent paragraph. If the tool description should 279 * only have a single paragraph, then this method may return {@code null} or 280 * an empty list. 281 * 282 * @return Additional paragraphs that should be included in the description 283 * for this tool, or {@code null} or an empty list if only a single 284 * description paragraph (whose text is returned by the 285 * {@code getToolDescription} method) is needed. 286 */ 287 @Override() 288 @NotNull() 289 public List<String> getAdditionalDescriptionParagraphs() 290 { 291 return Collections.singletonList( 292 "It creates an LDAP listener that uses a canned-response request " + 293 "handler to return a predefined response to all requests. It" + 294 "then invokes another tool (either searchrate, modrate, " + 295 "authrate, or search-and-mod-rate) to issue concurrent " + 296 "requests against that listener as quickly as possible."); 297 } 298 299 300 301 /** 302 * Retrieves a version string for this tool, if available. 303 * 304 * @return A version string for this tool, or {@code null} if none is 305 * available. 306 */ 307 @Override() 308 @NotNull() 309 public String getToolVersion() 310 { 311 return Version.NUMERIC_VERSION_STRING; 312 } 313 314 315 316 /** 317 * Indicates whether this tool should provide support for an interactive mode, 318 * in which the tool offers a mode in which the arguments can be provided in 319 * a text-driven menu rather than requiring them to be given on the command 320 * line. If interactive mode is supported, it may be invoked using the 321 * "--interactive" argument. Alternately, if interactive mode is supported 322 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then 323 * interactive mode may be invoked by simply launching the tool without any 324 * arguments. 325 * 326 * @return {@code true} if this tool supports interactive mode, or 327 * {@code false} if not. 328 */ 329 @Override() 330 public boolean supportsInteractiveMode() 331 { 332 return true; 333 } 334 335 336 337 /** 338 * Indicates whether this tool defaults to launching in interactive mode if 339 * the tool is invoked without any command-line arguments. This will only be 340 * used if {@link #supportsInteractiveMode()} returns {@code true}. 341 * 342 * @return {@code true} if this tool defaults to using interactive mode if 343 * launched without any command-line arguments, or {@code false} if 344 * not. 345 */ 346 @Override() 347 public boolean defaultsToInteractiveMode() 348 { 349 return true; 350 } 351 352 353 354 /** 355 * Indicates whether this tool supports the use of a properties file for 356 * specifying default values for arguments that aren't specified on the 357 * command line. 358 * 359 * @return {@code true} if this tool supports the use of a properties file 360 * for specifying default values for arguments that aren't specified 361 * on the command line, or {@code false} if not. 362 */ 363 @Override() 364 public boolean supportsPropertiesFile() 365 { 366 return true; 367 } 368 369 370 371 /** 372 * Indicates whether this tool should provide arguments for redirecting output 373 * to a file. If this method returns {@code true}, then the tool will offer 374 * an "--outputFile" argument that will specify the path to a file to which 375 * all standard output and standard error content will be written, and it will 376 * also offer a "--teeToStandardOut" argument that can only be used if the 377 * "--outputFile" argument is present and will cause all output to be written 378 * to both the specified output file and to standard output. 379 * 380 * @return {@code true} if this tool should provide arguments for redirecting 381 * output to a file, or {@code false} if not. 382 */ 383 @Override() 384 protected boolean supportsOutputFile() 385 { 386 return true; 387 } 388 389 390 391 /** 392 * Retrieves an optional message that may provide additional information about 393 * the way that the tool completed its processing. For example if the tool 394 * exited with an error message, it may be useful for this method to return 395 * that error message. 396 * <BR><BR> 397 * The message returned by this method is intended for purposes and is not 398 * meant to be parsed or programmatically interpreted. 399 * 400 * @return An optional message that may provide additional information about 401 * the completion state for this tool, or {@code null} if no 402 * completion message is available. 403 */ 404 @Override() 405 @Nullable() 406 protected String getToolCompletionMessage() 407 { 408 return completionMessage.get(); 409 } 410 411 412 413 /** 414 * Adds the command-line arguments supported for use with this tool to the 415 * provided argument parser. The tool may need to retain references to the 416 * arguments (and/or the argument parser, if trailing arguments are allowed) 417 * to it in order to obtain their values for use in later processing. 418 * 419 * @param parser The argument parser to which the arguments are to be added. 420 * 421 * @throws ArgumentException If a problem occurs while adding any of the 422 * tool-specific arguments to the provided 423 * argument parser. 424 */ 425 @Override() 426 public void addToolArguments(@NotNull final ArgumentParser parser) 427 throws ArgumentException 428 { 429 toolArg = new StringArgument(null, "tool", true, 1, 430 "{searchrate|modrate|authrate|search-and-mod-rate}", 431 "The tool to invoke against the LDAP listener. It may be one of " + 432 "searchrate, modrate, authrate, or search-and-mod-rate. If " + 433 "this is not provided, then the searchrate tool will be invoked.", 434 StaticUtils.setOf(TOOL_NAME_SEARCHRATE, 435 TOOL_NAME_MODRATE, 436 TOOL_NAME_AUTHRATE, 437 TOOL_NAME_SEARCH_AND_MOD_RATE), 438 TOOL_NAME_SEARCHRATE); 439 toolArg.addLongIdentifier("toolName", true); 440 toolArg.addLongIdentifier("tool-name", true); 441 parser.addArgument(toolArg); 442 443 444 numThreadsArg = new IntegerArgument('t', "numThreads", true, 1, "{num}", 445 "The number of concurrent threads (each using its own connection) " + 446 "to use to process requests. If this is not provided, then a " + 447 "single thread will be used.", 448 1, Integer.MAX_VALUE, 1); 449 numThreadsArg.addLongIdentifier("num-threads", true); 450 numThreadsArg.addLongIdentifier("threads", true); 451 parser.addArgument(numThreadsArg); 452 453 454 entriesPerSearchArg = new IntegerArgument(null, "entriesPerSearch", true, 455 1, "{num}", 456 "The number of entries to return in response to each search " + 457 "request. If this is provided, the value must be between 0 " + 458 "and 100. If it is not provided, then a single entry will be " + 459 "returned.", 460 0, 100, 1); 461 entriesPerSearchArg.addLongIdentifier("entries-per-search", true); 462 entriesPerSearchArg.addLongIdentifier("numEntries", true); 463 entriesPerSearchArg.addLongIdentifier("num-entries", true); 464 entriesPerSearchArg.addLongIdentifier("entries", true); 465 parser.addArgument(entriesPerSearchArg); 466 467 468 bindOnlyArg = new BooleanArgument(null, "bindOnly", 1, 469 "Indicates that the authrate tool should only issue bind requests. " + 470 "If this is not provided, the authrate tool will perform both " + 471 "search and bind operations. This argument will only be used " + 472 "in conjunction with the authrate tool."); 473 bindOnlyArg.addLongIdentifier("bind-only", true); 474 parser.addArgument(bindOnlyArg); 475 476 477 resultCodeArg = new IntegerArgument(null, "resultCode", true, 1, 478 "{intValue}", 479 "The integer value for the result code to return in response to " + 480 "each request. If this is not provided, then a result code of " + 481 "0 (success) will be returned.", 482 0, Integer.MAX_VALUE, ResultCode.SUCCESS_INT_VALUE); 483 resultCodeArg.addLongIdentifier("result-code", true); 484 parser.addArgument(resultCodeArg); 485 486 487 diagnosticMessageArg = new StringArgument(null, "diagnosticMessage", false, 488 1, "{message}", 489 "The diagnostic message to return in response to each request. If " + 490 "this is not provided, then no diagnostic message will be " + 491 "returned."); 492 diagnosticMessageArg.addLongIdentifier("diagnostic-message", true); 493 diagnosticMessageArg.addLongIdentifier("errorMessage", true); 494 diagnosticMessageArg.addLongIdentifier("error-message", true); 495 diagnosticMessageArg.addLongIdentifier("message", true); 496 parser.addArgument(diagnosticMessageArg); 497 498 499 useSSLArg = new BooleanArgument('Z', "useSSL", 1, 500 "Encrypt communication with SSL. If this argument is not provided, " + 501 "then the communication will not be encrypted."); 502 useSSLArg.addLongIdentifier("use-ssl", true); 503 useSSLArg.addLongIdentifier("ssl", true); 504 useSSLArg.addLongIdentifier("useTLS", true); 505 useSSLArg.addLongIdentifier("use-tls", true); 506 useSSLArg.addLongIdentifier("tls", true); 507 parser.addArgument(useSSLArg); 508 509 510 numIntervalsArg = new IntegerArgument('I', "numIntervals", false, 1, 511 "{num}", 512 "The number of intervals to use when running the performance " + 513 "measurement tool. If this argument is provided in " + 514 "conjunction with the --warmUpIntervals argument, then the " + 515 "warm-up intervals will not be included in this count, and the " + 516 "total number of intervals run will be the sum of the two " + 517 "values. If this argument is not provided, then the tool will " + 518 "run until it is interrupted (e.g., by pressing Ctrl+C or by " + 519 "killing the underlying Java process).", 520 0, Integer.MAX_VALUE); 521 numIntervalsArg.addLongIdentifier("num-intervals", true); 522 numIntervalsArg.addLongIdentifier("intervals", true); 523 parser.addArgument(numIntervalsArg); 524 525 526 intervalDurationSecondsArg = new IntegerArgument('i', 527 "intervalDurationSeconds", true, 1, "{num}", 528 "The length of time in seconds to use for each tool interval (that " + 529 "is, the length of time between each line of output giving " + 530 "statistical information for operations processed in that " + 531 "interval). If this is not provided, then a default interval " + 532 "duration of five seconds will be used.", 533 1, Integer.MAX_VALUE, 5); 534 intervalDurationSecondsArg.addLongIdentifier("interval-duration-seconds", 535 true); 536 intervalDurationSecondsArg.addLongIdentifier("intervalDuration", true); 537 intervalDurationSecondsArg.addLongIdentifier("interval-duration", true); 538 parser.addArgument(intervalDurationSecondsArg); 539 540 541 warmUpIntervalsArg = new IntegerArgument(null, "warmUpIntervals", true, 1, 542 "{num}", 543 "The number of intervals to run before starting to actually " + 544 "collect statistics to include in the final result. This can " + 545 "give the JVM and JIT a chance to identify and optimize " + 546 "hotspots in the code for the best and most stable " + 547 "performance. If this is not provided, then no warm-up " + 548 "intervals will be used and the tool will start collecting " + 549 "statistics right away.", 550 0, Integer.MAX_VALUE, 0); 551 warmUpIntervalsArg.addLongIdentifier("warm-up-intervals", true); 552 warmUpIntervalsArg.addLongIdentifier("warmup-intervals", true); 553 warmUpIntervalsArg.addLongIdentifier("warmUp", true); 554 warmUpIntervalsArg.addLongIdentifier("warm-up", true); 555 parser.addArgument(warmUpIntervalsArg); 556 } 557 558 559 560 /** 561 * Performs the core set of processing for this tool. 562 * 563 * @return A result code that indicates whether the processing completed 564 * successfully. 565 */ 566 @Override() 567 @NotNull() 568 public ResultCode doToolProcessing() 569 { 570 // Create the socket factory to use for accepting connections. If the 571 // --useSSL argument was provided, then create a temporary keystore and 572 // generate a certificate in it. 573 final ServerSocketFactory serverSocketFactory; 574 if (useSSLArg.isPresent()) 575 { 576 try 577 { 578 final File keyStoreFile = File.createTempFile( 579 "test-ldap-sdk-performance-keystore-", ".jks"); 580 keyStoreFile.deleteOnExit(); 581 keyStoreFile.delete(); 582 583 final ByteArrayOutputStream out = new ByteArrayOutputStream(); 584 final ResultCode manageCertificatesResultCode = 585 ManageCertificates.main(null, out, out, 586 "generate-self-signed-certificate", 587 "--keystore", keyStoreFile.getAbsolutePath(), 588 "--keystore-password", keyStoreFile.getAbsolutePath(), 589 "--keystore-type", "JKS", 590 "--alias", "server-cert", 591 "--subject-dn", "CN=Test LDAP SDK Performance"); 592 if (manageCertificatesResultCode != ResultCode.SUCCESS) 593 { 594 final String message = "ERROR: Unable to use the " + 595 "manage-certificates tool to generate a self-signed server " + 596 "certificate to use for SSL communication."; 597 completionMessage.compareAndSet(null, message); 598 wrapErr(0, WRAP_COLUMN, message); 599 err(); 600 wrapErr(0, WRAP_COLUMN, "The manage-certificates output was:"); 601 err(); 602 err(StaticUtils.toUTF8String(out.toByteArray())); 603 return manageCertificatesResultCode; 604 } 605 606 final SSLUtil sslUtil = new SSLUtil( 607 new KeyStoreKeyManager(keyStoreFile, 608 keyStoreFile.getAbsolutePath().toCharArray(), 609 "JKS", "server-cert"), 610 new TrustAllTrustManager()); 611 serverSocketFactory = sslUtil.createSSLServerSocketFactory(); 612 } 613 catch (final Exception e) 614 { 615 Debug.debugException(e); 616 617 final String message = "ERROR: Unable to initialize support for SSL " + 618 "communication: " + StaticUtils.getExceptionMessage(e); 619 completionMessage.compareAndSet(null, message); 620 wrapErr(0, WRAP_COLUMN, message); 621 return ResultCode.LOCAL_ERROR; 622 } 623 } 624 else 625 { 626 serverSocketFactory = ServerSocketFactory.getDefault(); 627 } 628 629 630 // Create the search result entries to return in response to each search. 631 final int numEntries = entriesPerSearchArg.getValue(); 632 final List<Entry> entries = new ArrayList<>(numEntries); 633 for (int i=1; i <= numEntries; i++) 634 { 635 entries.add(new Entry( 636 "uid=user." + i + ",ou=People,dc=example,dc=com", 637 new Attribute("objectClass", "top", "person", "organizationalPerson", 638 "inetOrgPerson"), 639 new Attribute("uid", "user." + i), 640 new Attribute("givenName", "User"), 641 new Attribute("sn", String.valueOf(i)), 642 new Attribute("cn", "User " + i), 643 new Attribute("mail", "user." + i + "@example.com"), 644 new Attribute("userPassword", "password"))); 645 } 646 647 648 // Create a canned response request handler to use to return the responses. 649 final CannedResponseRequestHandler cannedResponseRequestHandler = 650 new CannedResponseRequestHandler( 651 ResultCode.valueOf(resultCodeArg.getValue()), 652 null, // Matched DN 653 diagnosticMessageArg.getValue(), 654 Collections.<String>emptyList(), // Referral URLs 655 entries, 656 Collections.<SearchResultReference>emptyList()); 657 658 659 // Create the LDAP listener to handle the requests. 660 final LDAPListenerConfig listenerConfig = 661 new LDAPListenerConfig(0, cannedResponseRequestHandler); 662 listenerConfig.setServerSocketFactory(serverSocketFactory); 663 664 final LDAPListener ldapListener = new LDAPListener(listenerConfig); 665 try 666 { 667 ldapListener.startListening(); 668 } 669 catch (final Exception e) 670 { 671 Debug.debugException(e); 672 673 final String message = "ERROR: Unable to start listening for client " + 674 "connections: " + StaticUtils.getExceptionMessage(e); 675 completionMessage.compareAndSet(null, message); 676 wrapErr(0, WRAP_COLUMN, message); 677 return ResultCode.LOCAL_ERROR; 678 } 679 680 try 681 { 682 final int listenPort = ldapListener.getListenPort(); 683 final String toolName = StaticUtils.toLowerCase(toolArg.getValue()); 684 switch (toolName) 685 { 686 case TOOL_NAME_SEARCHRATE: 687 return invokeSearchRate(listenPort); 688 case TOOL_NAME_MODRATE: 689 return invokeModRate(listenPort); 690 case TOOL_NAME_AUTHRATE: 691 return invokeAuthRate(listenPort); 692 case TOOL_NAME_SEARCH_AND_MOD_RATE: 693 return invokeSearchAndModRate(listenPort); 694 default: 695 // This should never happen. 696 final String message = "ERROR: Unrecognized tool name: " + toolName; 697 completionMessage.compareAndSet(null, message); 698 wrapErr(0, WRAP_COLUMN, message); 699 return ResultCode.PARAM_ERROR; 700 } 701 } 702 finally 703 { 704 ldapListener.shutDown(true); 705 } 706 } 707 708 709 710 /** 711 * Invokes the {@link SearchRate} tool with an appropriate set of arguments. 712 * 713 * @param listenPort The port on which the LDAP listener is listening. 714 * 715 * @return The result code obtained from the {@code SearchRate} tool. 716 */ 717 @NotNull() 718 private ResultCode invokeSearchRate(final int listenPort) 719 { 720 final List<String> searchRateArgs = new ArrayList<>(); 721 722 searchRateArgs.add("--hostname"); 723 searchRateArgs.add("localhost"); 724 725 searchRateArgs.add("--port"); 726 searchRateArgs.add(String.valueOf(listenPort)); 727 728 if (useSSLArg.isPresent()) 729 { 730 searchRateArgs.add("--useSSL"); 731 searchRateArgs.add("--trustAll"); 732 } 733 734 searchRateArgs.add("--baseDN"); 735 searchRateArgs.add("dc=example,dc=com"); 736 737 searchRateArgs.add("--scope"); 738 searchRateArgs.add("sub"); 739 740 searchRateArgs.add("--filter"); 741 searchRateArgs.add("(objectClass=*)"); 742 743 searchRateArgs.add("--numThreads"); 744 searchRateArgs.add(String.valueOf(numThreadsArg.getValue())); 745 746 if (numIntervalsArg.isPresent()) 747 { 748 searchRateArgs.add("--numIntervals"); 749 searchRateArgs.add(String.valueOf(numIntervalsArg.getValue())); 750 } 751 752 if (intervalDurationSecondsArg.isPresent()) 753 { 754 searchRateArgs.add("--intervalDuration"); 755 searchRateArgs.add(String.valueOf( 756 intervalDurationSecondsArg.getValue())); 757 } 758 759 if (warmUpIntervalsArg.isPresent()) 760 { 761 searchRateArgs.add("--warmUpIntervals"); 762 searchRateArgs.add(String.valueOf(warmUpIntervalsArg.getValue())); 763 } 764 765 final String[] searchRateArgsArray = 766 searchRateArgs.toArray(StaticUtils.NO_STRINGS); 767 768 final SearchRate searchRate = new SearchRate(getOut(), getErr()); 769 770 final ResultCode searchRateResultCode = 771 searchRate.runTool(searchRateArgsArray); 772 if (searchRateResultCode == ResultCode.SUCCESS) 773 { 774 final String message = "The searchrate tool completed successfully."; 775 completionMessage.compareAndSet(null, message); 776 wrapOut(0, WRAP_COLUMN, message); 777 } 778 else 779 { 780 final String message = 781 "ERROR: The searchrate tool exited with error result code " + 782 searchRateResultCode + '.'; 783 completionMessage.compareAndSet(null, message); 784 wrapErr(0, WRAP_COLUMN, message); 785 } 786 787 return searchRateResultCode; 788 } 789 790 791 792 /** 793 * Invokes the {@link ModRate} tool with an appropriate set of arguments. 794 * 795 * @param listenPort The port on which the LDAP listener is listening. 796 * 797 * @return The result code obtained from the {@code ModRate} tool. 798 */ 799 @NotNull() 800 private ResultCode invokeModRate(final int listenPort) 801 { 802 final List<String> modRateArgs = new ArrayList<>(); 803 804 modRateArgs.add("--hostname"); 805 modRateArgs.add("localhost"); 806 807 modRateArgs.add("--port"); 808 modRateArgs.add(String.valueOf(listenPort)); 809 810 if (useSSLArg.isPresent()) 811 { 812 modRateArgs.add("--useSSL"); 813 modRateArgs.add("--trustAll"); 814 } 815 816 modRateArgs.add("--entryDN"); 817 modRateArgs.add("dc=example,dc=com"); 818 819 modRateArgs.add("--attribute"); 820 modRateArgs.add("description"); 821 822 modRateArgs.add("--valuePattern"); 823 modRateArgs.add("value"); 824 825 modRateArgs.add("--numThreads"); 826 modRateArgs.add(String.valueOf(numThreadsArg.getValue())); 827 828 if (numIntervalsArg.isPresent()) 829 { 830 modRateArgs.add("--numIntervals"); 831 modRateArgs.add(String.valueOf(numIntervalsArg.getValue())); 832 } 833 834 if (intervalDurationSecondsArg.isPresent()) 835 { 836 modRateArgs.add("--intervalDuration"); 837 modRateArgs.add(String.valueOf( 838 intervalDurationSecondsArg.getValue())); 839 } 840 841 if (warmUpIntervalsArg.isPresent()) 842 { 843 modRateArgs.add("--warmUpIntervals"); 844 modRateArgs.add(String.valueOf(warmUpIntervalsArg.getValue())); 845 } 846 847 final String[] modRateArgsArray = 848 modRateArgs.toArray(StaticUtils.NO_STRINGS); 849 850 final ModRate modRate = new ModRate(getOut(), getErr()); 851 852 final ResultCode modRateResultCode = 853 modRate.runTool(modRateArgsArray); 854 if (modRateResultCode == ResultCode.SUCCESS) 855 { 856 final String message = "The modrate tool completed successfully."; 857 completionMessage.compareAndSet(null, message); 858 wrapOut(0, WRAP_COLUMN, message); 859 } 860 else 861 { 862 final String message = 863 "ERROR: The modrate tool exited with error result code " + 864 modRateResultCode + '.'; 865 completionMessage.compareAndSet(null, message); 866 wrapErr(0, WRAP_COLUMN, message); 867 } 868 869 return modRateResultCode; 870 } 871 872 873 874 /** 875 * Invokes the {@link AuthRate} tool with an appropriate set of arguments. 876 * 877 * @param listenPort The port on which the LDAP listener is listening. 878 * 879 * @return The result code obtained from the {@code AuthRate} tool. 880 */ 881 @NotNull() 882 private ResultCode invokeAuthRate(final int listenPort) 883 { 884 final List<String> authRateArgs = new ArrayList<>(); 885 886 authRateArgs.add("--hostname"); 887 authRateArgs.add("localhost"); 888 889 authRateArgs.add("--port"); 890 authRateArgs.add(String.valueOf(listenPort)); 891 892 if (useSSLArg.isPresent()) 893 { 894 authRateArgs.add("--useSSL"); 895 authRateArgs.add("--trustAll"); 896 } 897 898 if (bindOnlyArg.isPresent()) 899 { 900 authRateArgs.add("--bindOnly"); 901 902 authRateArgs.add("--baseDN"); 903 authRateArgs.add("uid=user.1,ou=People,dc=example,dc=com"); 904 } 905 else 906 { 907 authRateArgs.add("--baseDN"); 908 authRateArgs.add("dc=example,dc=com"); 909 910 authRateArgs.add("--scope"); 911 authRateArgs.add("sub"); 912 913 authRateArgs.add("--filter"); 914 authRateArgs.add("(uid=user.1)"); 915 } 916 917 authRateArgs.add("--credentials"); 918 authRateArgs.add("password"); 919 920 authRateArgs.add("--numThreads"); 921 authRateArgs.add(String.valueOf(numThreadsArg.getValue())); 922 923 if (numIntervalsArg.isPresent()) 924 { 925 authRateArgs.add("--numIntervals"); 926 authRateArgs.add(String.valueOf(numIntervalsArg.getValue())); 927 } 928 929 if (intervalDurationSecondsArg.isPresent()) 930 { 931 authRateArgs.add("--intervalDuration"); 932 authRateArgs.add(String.valueOf( 933 intervalDurationSecondsArg.getValue())); 934 } 935 936 if (warmUpIntervalsArg.isPresent()) 937 { 938 authRateArgs.add("--warmUpIntervals"); 939 authRateArgs.add(String.valueOf(warmUpIntervalsArg.getValue())); 940 } 941 942 final String[] authRateArgsArray = 943 authRateArgs.toArray(StaticUtils.NO_STRINGS); 944 945 final AuthRate authRate = new AuthRate(getOut(), getErr()); 946 947 final ResultCode authRateResultCode = 948 authRate.runTool(authRateArgsArray); 949 if (authRateResultCode == ResultCode.SUCCESS) 950 { 951 final String message = "The authrate tool completed successfully."; 952 completionMessage.compareAndSet(null, message); 953 wrapOut(0, WRAP_COLUMN, message); 954 } 955 else 956 { 957 final String message = 958 "ERROR: The authrate tool exited with error result code " + 959 authRateResultCode + '.'; 960 completionMessage.compareAndSet(null, message); 961 wrapErr(0, WRAP_COLUMN, message); 962 } 963 964 return authRateResultCode; 965 } 966 967 968 969 /** 970 * Invokes the {@link SearchAndModRate} tool with an appropriate set of 971 * arguments. 972 * 973 * @param listenPort The port on which the LDAP listener is listening. 974 * 975 * @return The result code obtained from the {@code SearchAndModRate} tool. 976 */ 977 @NotNull() 978 private ResultCode invokeSearchAndModRate(final int listenPort) 979 { 980 final List<String> searchAndModRateArgs = new ArrayList<>(); 981 982 searchAndModRateArgs.add("--hostname"); 983 searchAndModRateArgs.add("localhost"); 984 985 searchAndModRateArgs.add("--port"); 986 searchAndModRateArgs.add(String.valueOf(listenPort)); 987 988 if (useSSLArg.isPresent()) 989 { 990 searchAndModRateArgs.add("--useSSL"); 991 searchAndModRateArgs.add("--trustAll"); 992 } 993 994 searchAndModRateArgs.add("--baseDN"); 995 searchAndModRateArgs.add("dc=example,dc=com"); 996 997 searchAndModRateArgs.add("--scope"); 998 searchAndModRateArgs.add("sub"); 999 1000 searchAndModRateArgs.add("--filter"); 1001 searchAndModRateArgs.add("(objectClass=*)"); 1002 1003 searchAndModRateArgs.add("--modifyAttribute"); 1004 searchAndModRateArgs.add("description"); 1005 1006 searchAndModRateArgs.add("--valueLength"); 1007 searchAndModRateArgs.add("10"); 1008 1009 searchAndModRateArgs.add("--numThreads"); 1010 searchAndModRateArgs.add(String.valueOf(numThreadsArg.getValue())); 1011 1012 if (numIntervalsArg.isPresent()) 1013 { 1014 searchAndModRateArgs.add("--numIntervals"); 1015 searchAndModRateArgs.add(String.valueOf(numIntervalsArg.getValue())); 1016 } 1017 1018 if (intervalDurationSecondsArg.isPresent()) 1019 { 1020 searchAndModRateArgs.add("--intervalDuration"); 1021 searchAndModRateArgs.add(String.valueOf( 1022 intervalDurationSecondsArg.getValue())); 1023 } 1024 1025 if (warmUpIntervalsArg.isPresent()) 1026 { 1027 searchAndModRateArgs.add("--warmUpIntervals"); 1028 searchAndModRateArgs.add(String.valueOf(warmUpIntervalsArg.getValue())); 1029 } 1030 1031 final String[] searchAndModRateArgsArray = 1032 searchAndModRateArgs.toArray(StaticUtils.NO_STRINGS); 1033 1034 final SearchAndModRate searchAndModRate = 1035 new SearchAndModRate(getOut(), getErr()); 1036 1037 final ResultCode searchAndModRateResultCode = 1038 searchAndModRate.runTool(searchAndModRateArgsArray); 1039 if (searchAndModRateResultCode == ResultCode.SUCCESS) 1040 { 1041 final String message = 1042 "The search-and-mod-rate tool completed successfully."; 1043 completionMessage.compareAndSet(null, message); 1044 wrapOut(0, WRAP_COLUMN, message); 1045 } 1046 else 1047 { 1048 final String message = 1049 "ERROR: The search-and-mod-rate tool exited with error result " + 1050 "code " + searchAndModRateResultCode + '.'; 1051 completionMessage.compareAndSet(null, message); 1052 wrapErr(0, WRAP_COLUMN, message); 1053 } 1054 1055 return searchAndModRateResultCode; 1056 } 1057 1058 1059 1060 /** 1061 * Retrieves a set of information that may be used to generate example usage 1062 * information. Each element in the returned map should consist of a map 1063 * between an example set of arguments and a string that describes the 1064 * behavior of the tool when invoked with that set of arguments. 1065 * 1066 * @return A set of information that may be used to generate example usage 1067 * information. It may be {@code null} or empty if no example usage 1068 * information is available. 1069 */ 1070 @Override() 1071 @NotNull() 1072 public LinkedHashMap<String[],String> getExampleUsages() 1073 { 1074 final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(); 1075 1076 examples.put( 1077 new String[] 1078 { 1079 "--numThreads", "10" 1080 }, 1081 "Test LDAP SDK performance with the searchrate tool using ten " + 1082 "concurrent threads. Communication will use an insecure " + 1083 "connection, and each search will return a success result with " + 1084 "a single matching entry. The tool will continue to run until " + 1085 "it is interrupted."); 1086 1087 examples.put( 1088 new String[] 1089 { 1090 "--tool", "modrate", 1091 "--numThreads", "10", 1092 "--useSSL", 1093 "--resultCode", "32", 1094 "--diagnosticMessage", "The base entry does not exist", 1095 "--warmUpIntervals", "5", 1096 "--numIntervals", "10", 1097 "--intervalDurationSeconds", "5" 1098 }, 1099 "Test LDAP SDK performance with the modrate tool using ten " + 1100 "concurrent threads over SSL-encrypted connections. Each " + 1101 "modify will return an error result with a result code of 32 " + 1102 "(noSuchObject) and a diagnostic message of 'The target entry " + 1103 "does not exist'. The tool will run five warm-up intervals " + 1104 "of five seconds each, and then ten 5-second intervals in " + 1105 "which it will capture statistics. The tool will exit after " + 1106 "those last ten intervals have completed."); 1107 1108 return examples; 1109 } 1110}