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