001/* 002 * Copyright 2020-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2020-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2020-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.examples; 037 038 039 040import java.io.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 * Indicates whether this tool supports the ability to generate a debug log 394 * file. If this method returns {@code true}, then the tool will expose 395 * additional arguments that can control debug logging. 396 * 397 * @return {@code true} if this tool supports the ability to generate a debug 398 * log file, or {@code false} if not. 399 */ 400 @Override() 401 protected boolean supportsDebugLogging() 402 { 403 return true; 404 } 405 406 407 408 /** 409 * Retrieves an optional message that may provide additional information about 410 * the way that the tool completed its processing. For example if the tool 411 * exited with an error message, it may be useful for this method to return 412 * that error message. 413 * <BR><BR> 414 * The message returned by this method is intended for purposes and is not 415 * meant to be parsed or programmatically interpreted. 416 * 417 * @return An optional message that may provide additional information about 418 * the completion state for this tool, or {@code null} if no 419 * completion message is available. 420 */ 421 @Override() 422 @Nullable() 423 protected String getToolCompletionMessage() 424 { 425 return completionMessage.get(); 426 } 427 428 429 430 /** 431 * Adds the command-line arguments supported for use with this tool to the 432 * provided argument parser. The tool may need to retain references to the 433 * arguments (and/or the argument parser, if trailing arguments are allowed) 434 * to it in order to obtain their values for use in later processing. 435 * 436 * @param parser The argument parser to which the arguments are to be added. 437 * 438 * @throws ArgumentException If a problem occurs while adding any of the 439 * tool-specific arguments to the provided 440 * argument parser. 441 */ 442 @Override() 443 public void addToolArguments(@NotNull final ArgumentParser parser) 444 throws ArgumentException 445 { 446 toolArg = new StringArgument(null, "tool", true, 1, 447 "{searchrate|modrate|authrate|search-and-mod-rate}", 448 "The tool to invoke against the LDAP listener. It may be one of " + 449 "searchrate, modrate, authrate, or search-and-mod-rate. If " + 450 "this is not provided, then the searchrate tool will be invoked.", 451 StaticUtils.setOf(TOOL_NAME_SEARCHRATE, 452 TOOL_NAME_MODRATE, 453 TOOL_NAME_AUTHRATE, 454 TOOL_NAME_SEARCH_AND_MOD_RATE), 455 TOOL_NAME_SEARCHRATE); 456 toolArg.addLongIdentifier("toolName", true); 457 toolArg.addLongIdentifier("tool-name", true); 458 parser.addArgument(toolArg); 459 460 461 numThreadsArg = new IntegerArgument('t', "numThreads", true, 1, "{num}", 462 "The number of concurrent threads (each using its own connection) " + 463 "to use to process requests. If this is not provided, then a " + 464 "single thread will be used.", 465 1, Integer.MAX_VALUE, 1); 466 numThreadsArg.addLongIdentifier("num-threads", true); 467 numThreadsArg.addLongIdentifier("threads", true); 468 parser.addArgument(numThreadsArg); 469 470 471 entriesPerSearchArg = new IntegerArgument(null, "entriesPerSearch", true, 472 1, "{num}", 473 "The number of entries to return in response to each search " + 474 "request. If this is provided, the value must be between 0 " + 475 "and 100. If it is not provided, then a single entry will be " + 476 "returned.", 477 0, 100, 1); 478 entriesPerSearchArg.addLongIdentifier("entries-per-search", true); 479 entriesPerSearchArg.addLongIdentifier("numEntries", true); 480 entriesPerSearchArg.addLongIdentifier("num-entries", true); 481 entriesPerSearchArg.addLongIdentifier("entries", true); 482 parser.addArgument(entriesPerSearchArg); 483 484 485 bindOnlyArg = new BooleanArgument(null, "bindOnly", 1, 486 "Indicates that the authrate tool should only issue bind requests. " + 487 "If this is not provided, the authrate tool will perform both " + 488 "search and bind operations. This argument will only be used " + 489 "in conjunction with the authrate tool."); 490 bindOnlyArg.addLongIdentifier("bind-only", true); 491 parser.addArgument(bindOnlyArg); 492 493 494 resultCodeArg = new IntegerArgument(null, "resultCode", true, 1, 495 "{intValue}", 496 "The integer value for the result code to return in response to " + 497 "each request. If this is not provided, then a result code of " + 498 "0 (success) will be returned.", 499 0, Integer.MAX_VALUE, ResultCode.SUCCESS_INT_VALUE); 500 resultCodeArg.addLongIdentifier("result-code", true); 501 parser.addArgument(resultCodeArg); 502 503 504 diagnosticMessageArg = new StringArgument(null, "diagnosticMessage", false, 505 1, "{message}", 506 "The diagnostic message to return in response to each request. If " + 507 "this is not provided, then no diagnostic message will be " + 508 "returned."); 509 diagnosticMessageArg.addLongIdentifier("diagnostic-message", true); 510 diagnosticMessageArg.addLongIdentifier("errorMessage", true); 511 diagnosticMessageArg.addLongIdentifier("error-message", true); 512 diagnosticMessageArg.addLongIdentifier("message", true); 513 parser.addArgument(diagnosticMessageArg); 514 515 516 useSSLArg = new BooleanArgument('Z', "useSSL", 1, 517 "Encrypt communication with SSL. If this argument is not provided, " + 518 "then the communication will not be encrypted."); 519 useSSLArg.addLongIdentifier("use-ssl", true); 520 useSSLArg.addLongIdentifier("ssl", true); 521 useSSLArg.addLongIdentifier("useTLS", true); 522 useSSLArg.addLongIdentifier("use-tls", true); 523 useSSLArg.addLongIdentifier("tls", true); 524 parser.addArgument(useSSLArg); 525 526 527 numIntervalsArg = new IntegerArgument('I', "numIntervals", false, 1, 528 "{num}", 529 "The number of intervals to use when running the performance " + 530 "measurement tool. If this argument is provided in " + 531 "conjunction with the --warmUpIntervals argument, then the " + 532 "warm-up intervals will not be included in this count, and the " + 533 "total number of intervals run will be the sum of the two " + 534 "values. If this argument is not provided, then the tool will " + 535 "run until it is interrupted (e.g., by pressing Ctrl+C or by " + 536 "killing the underlying Java process).", 537 0, Integer.MAX_VALUE); 538 numIntervalsArg.addLongIdentifier("num-intervals", true); 539 numIntervalsArg.addLongIdentifier("intervals", true); 540 parser.addArgument(numIntervalsArg); 541 542 543 intervalDurationSecondsArg = new IntegerArgument('i', 544 "intervalDurationSeconds", true, 1, "{num}", 545 "The length of time in seconds to use for each tool interval (that " + 546 "is, the length of time between each line of output giving " + 547 "statistical information for operations processed in that " + 548 "interval). If this is not provided, then a default interval " + 549 "duration of five seconds will be used.", 550 1, Integer.MAX_VALUE, 5); 551 intervalDurationSecondsArg.addLongIdentifier("interval-duration-seconds", 552 true); 553 intervalDurationSecondsArg.addLongIdentifier("intervalDuration", true); 554 intervalDurationSecondsArg.addLongIdentifier("interval-duration", true); 555 parser.addArgument(intervalDurationSecondsArg); 556 557 558 warmUpIntervalsArg = new IntegerArgument(null, "warmUpIntervals", true, 1, 559 "{num}", 560 "The number of intervals to run before starting to actually " + 561 "collect statistics to include in the final result. This can " + 562 "give the JVM and JIT a chance to identify and optimize " + 563 "hotspots in the code for the best and most stable " + 564 "performance. If this is not provided, then no warm-up " + 565 "intervals will be used and the tool will start collecting " + 566 "statistics right away.", 567 0, Integer.MAX_VALUE, 0); 568 warmUpIntervalsArg.addLongIdentifier("warm-up-intervals", true); 569 warmUpIntervalsArg.addLongIdentifier("warmup-intervals", true); 570 warmUpIntervalsArg.addLongIdentifier("warmUp", true); 571 warmUpIntervalsArg.addLongIdentifier("warm-up", true); 572 parser.addArgument(warmUpIntervalsArg); 573 } 574 575 576 577 /** 578 * Performs the core set of processing for this tool. 579 * 580 * @return A result code that indicates whether the processing completed 581 * successfully. 582 */ 583 @Override() 584 @NotNull() 585 public ResultCode doToolProcessing() 586 { 587 // Create the socket factory to use for accepting connections. If the 588 // --useSSL argument was provided, then create a temporary keystore and 589 // generate a certificate in it. 590 final ServerSocketFactory serverSocketFactory; 591 if (useSSLArg.isPresent()) 592 { 593 try 594 { 595 final File keyStoreFile = File.createTempFile( 596 "test-ldap-sdk-performance-keystore-", ".jks"); 597 keyStoreFile.deleteOnExit(); 598 keyStoreFile.delete(); 599 600 final ByteArrayOutputStream out = new ByteArrayOutputStream(); 601 final ResultCode manageCertificatesResultCode = 602 ManageCertificates.main(null, out, out, 603 "generate-self-signed-certificate", 604 "--keystore", keyStoreFile.getAbsolutePath(), 605 "--keystore-password", keyStoreFile.getAbsolutePath(), 606 "--keystore-type", CryptoHelper.KEY_STORE_TYPE_JKS, 607 "--alias", "server-cert", 608 "--subject-dn", "CN=Test LDAP SDK Performance"); 609 if (manageCertificatesResultCode != ResultCode.SUCCESS) 610 { 611 final String message = "ERROR: Unable to use the " + 612 "manage-certificates tool to generate a self-signed server " + 613 "certificate to use for SSL communication."; 614 completionMessage.compareAndSet(null, message); 615 wrapErr(0, WRAP_COLUMN, message); 616 err(); 617 wrapErr(0, WRAP_COLUMN, "The manage-certificates output was:"); 618 err(); 619 err(StaticUtils.toUTF8String(out.toByteArray())); 620 return manageCertificatesResultCode; 621 } 622 623 final SSLUtil sslUtil = new SSLUtil( 624 new KeyStoreKeyManager(keyStoreFile, 625 keyStoreFile.getAbsolutePath().toCharArray(), 626 CryptoHelper.KEY_STORE_TYPE_JKS, "server-cert"), 627 new TrustAllTrustManager()); 628 serverSocketFactory = sslUtil.createSSLServerSocketFactory(); 629 } 630 catch (final Exception e) 631 { 632 Debug.debugException(e); 633 634 final String message = "ERROR: Unable to initialize support for SSL " + 635 "communication: " + StaticUtils.getExceptionMessage(e); 636 completionMessage.compareAndSet(null, message); 637 wrapErr(0, WRAP_COLUMN, message); 638 return ResultCode.LOCAL_ERROR; 639 } 640 } 641 else 642 { 643 serverSocketFactory = ServerSocketFactory.getDefault(); 644 } 645 646 647 // Create the search result entries to return in response to each search. 648 final int numEntries = entriesPerSearchArg.getValue(); 649 final List<Entry> entries = new ArrayList<>(numEntries); 650 for (int i=1; i <= numEntries; i++) 651 { 652 entries.add(new Entry( 653 "uid=user." + i + ",ou=People,dc=example,dc=com", 654 new Attribute("objectClass", "top", "person", "organizationalPerson", 655 "inetOrgPerson"), 656 new Attribute("uid", "user." + i), 657 new Attribute("givenName", "User"), 658 new Attribute("sn", String.valueOf(i)), 659 new Attribute("cn", "User " + i), 660 new Attribute("mail", "user." + i + "@example.com"), 661 new Attribute("userPassword", "password"))); 662 } 663 664 665 // Create a canned response request handler to use to return the responses. 666 final CannedResponseRequestHandler cannedResponseRequestHandler = 667 new CannedResponseRequestHandler( 668 ResultCode.valueOf(resultCodeArg.getValue()), 669 null, // Matched DN 670 diagnosticMessageArg.getValue(), 671 Collections.<String>emptyList(), // Referral URLs 672 entries, 673 Collections.<SearchResultReference>emptyList()); 674 675 676 // Create the LDAP listener to handle the requests. 677 final LDAPListenerConfig listenerConfig = 678 new LDAPListenerConfig(0, cannedResponseRequestHandler); 679 listenerConfig.setServerSocketFactory(serverSocketFactory); 680 681 final LDAPListener ldapListener = new LDAPListener(listenerConfig); 682 try 683 { 684 ldapListener.startListening(); 685 } 686 catch (final Exception e) 687 { 688 Debug.debugException(e); 689 690 final String message = "ERROR: Unable to start listening for client " + 691 "connections: " + StaticUtils.getExceptionMessage(e); 692 completionMessage.compareAndSet(null, message); 693 wrapErr(0, WRAP_COLUMN, message); 694 return ResultCode.LOCAL_ERROR; 695 } 696 697 try 698 { 699 final int listenPort = ldapListener.getListenPort(); 700 final String toolName = StaticUtils.toLowerCase(toolArg.getValue()); 701 switch (toolName) 702 { 703 case TOOL_NAME_SEARCHRATE: 704 return invokeSearchRate(listenPort); 705 case TOOL_NAME_MODRATE: 706 return invokeModRate(listenPort); 707 case TOOL_NAME_AUTHRATE: 708 return invokeAuthRate(listenPort); 709 case TOOL_NAME_SEARCH_AND_MOD_RATE: 710 return invokeSearchAndModRate(listenPort); 711 default: 712 // This should never happen. 713 final String message = "ERROR: Unrecognized tool name: " + toolName; 714 completionMessage.compareAndSet(null, message); 715 wrapErr(0, WRAP_COLUMN, message); 716 return ResultCode.PARAM_ERROR; 717 } 718 } 719 finally 720 { 721 ldapListener.shutDown(true); 722 } 723 } 724 725 726 727 /** 728 * Invokes the {@link SearchRate} tool with an appropriate set of arguments. 729 * 730 * @param listenPort The port on which the LDAP listener is listening. 731 * 732 * @return The result code obtained from the {@code SearchRate} tool. 733 */ 734 @NotNull() 735 private ResultCode invokeSearchRate(final int listenPort) 736 { 737 final List<String> searchRateArgs = new ArrayList<>(); 738 739 searchRateArgs.add("--hostname"); 740 searchRateArgs.add("localhost"); 741 742 searchRateArgs.add("--port"); 743 searchRateArgs.add(String.valueOf(listenPort)); 744 745 if (useSSLArg.isPresent()) 746 { 747 searchRateArgs.add("--useSSL"); 748 searchRateArgs.add("--trustAll"); 749 } 750 751 searchRateArgs.add("--baseDN"); 752 searchRateArgs.add("dc=example,dc=com"); 753 754 searchRateArgs.add("--scope"); 755 searchRateArgs.add("sub"); 756 757 searchRateArgs.add("--filter"); 758 searchRateArgs.add("(objectClass=*)"); 759 760 searchRateArgs.add("--numThreads"); 761 searchRateArgs.add(String.valueOf(numThreadsArg.getValue())); 762 763 if (numIntervalsArg.isPresent()) 764 { 765 searchRateArgs.add("--numIntervals"); 766 searchRateArgs.add(String.valueOf(numIntervalsArg.getValue())); 767 } 768 769 if (intervalDurationSecondsArg.isPresent()) 770 { 771 searchRateArgs.add("--intervalDuration"); 772 searchRateArgs.add(String.valueOf( 773 intervalDurationSecondsArg.getValue())); 774 } 775 776 if (warmUpIntervalsArg.isPresent()) 777 { 778 searchRateArgs.add("--warmUpIntervals"); 779 searchRateArgs.add(String.valueOf(warmUpIntervalsArg.getValue())); 780 } 781 782 final String[] searchRateArgsArray = 783 searchRateArgs.toArray(StaticUtils.NO_STRINGS); 784 785 final SearchRate searchRate = new SearchRate(getOut(), getErr()); 786 787 final ResultCode searchRateResultCode = 788 searchRate.runTool(searchRateArgsArray); 789 if (searchRateResultCode == ResultCode.SUCCESS) 790 { 791 final String message = "The searchrate tool completed successfully."; 792 completionMessage.compareAndSet(null, message); 793 wrapOut(0, WRAP_COLUMN, message); 794 } 795 else 796 { 797 final String message = 798 "ERROR: The searchrate tool exited with error result code " + 799 searchRateResultCode + '.'; 800 completionMessage.compareAndSet(null, message); 801 wrapErr(0, WRAP_COLUMN, message); 802 } 803 804 return searchRateResultCode; 805 } 806 807 808 809 /** 810 * Invokes the {@link ModRate} tool with an appropriate set of arguments. 811 * 812 * @param listenPort The port on which the LDAP listener is listening. 813 * 814 * @return The result code obtained from the {@code ModRate} tool. 815 */ 816 @NotNull() 817 private ResultCode invokeModRate(final int listenPort) 818 { 819 final List<String> modRateArgs = new ArrayList<>(); 820 821 modRateArgs.add("--hostname"); 822 modRateArgs.add("localhost"); 823 824 modRateArgs.add("--port"); 825 modRateArgs.add(String.valueOf(listenPort)); 826 827 if (useSSLArg.isPresent()) 828 { 829 modRateArgs.add("--useSSL"); 830 modRateArgs.add("--trustAll"); 831 } 832 833 modRateArgs.add("--entryDN"); 834 modRateArgs.add("dc=example,dc=com"); 835 836 modRateArgs.add("--attribute"); 837 modRateArgs.add("description"); 838 839 modRateArgs.add("--valuePattern"); 840 modRateArgs.add("value"); 841 842 modRateArgs.add("--numThreads"); 843 modRateArgs.add(String.valueOf(numThreadsArg.getValue())); 844 845 if (numIntervalsArg.isPresent()) 846 { 847 modRateArgs.add("--numIntervals"); 848 modRateArgs.add(String.valueOf(numIntervalsArg.getValue())); 849 } 850 851 if (intervalDurationSecondsArg.isPresent()) 852 { 853 modRateArgs.add("--intervalDuration"); 854 modRateArgs.add(String.valueOf( 855 intervalDurationSecondsArg.getValue())); 856 } 857 858 if (warmUpIntervalsArg.isPresent()) 859 { 860 modRateArgs.add("--warmUpIntervals"); 861 modRateArgs.add(String.valueOf(warmUpIntervalsArg.getValue())); 862 } 863 864 final String[] modRateArgsArray = 865 modRateArgs.toArray(StaticUtils.NO_STRINGS); 866 867 final ModRate modRate = new ModRate(getOut(), getErr()); 868 869 final ResultCode modRateResultCode = 870 modRate.runTool(modRateArgsArray); 871 if (modRateResultCode == ResultCode.SUCCESS) 872 { 873 final String message = "The modrate tool completed successfully."; 874 completionMessage.compareAndSet(null, message); 875 wrapOut(0, WRAP_COLUMN, message); 876 } 877 else 878 { 879 final String message = 880 "ERROR: The modrate tool exited with error result code " + 881 modRateResultCode + '.'; 882 completionMessage.compareAndSet(null, message); 883 wrapErr(0, WRAP_COLUMN, message); 884 } 885 886 return modRateResultCode; 887 } 888 889 890 891 /** 892 * Invokes the {@link AuthRate} tool with an appropriate set of arguments. 893 * 894 * @param listenPort The port on which the LDAP listener is listening. 895 * 896 * @return The result code obtained from the {@code AuthRate} tool. 897 */ 898 @NotNull() 899 private ResultCode invokeAuthRate(final int listenPort) 900 { 901 final List<String> authRateArgs = new ArrayList<>(); 902 903 authRateArgs.add("--hostname"); 904 authRateArgs.add("localhost"); 905 906 authRateArgs.add("--port"); 907 authRateArgs.add(String.valueOf(listenPort)); 908 909 if (useSSLArg.isPresent()) 910 { 911 authRateArgs.add("--useSSL"); 912 authRateArgs.add("--trustAll"); 913 } 914 915 if (bindOnlyArg.isPresent()) 916 { 917 authRateArgs.add("--bindOnly"); 918 919 authRateArgs.add("--baseDN"); 920 authRateArgs.add("uid=user.1,ou=People,dc=example,dc=com"); 921 } 922 else 923 { 924 authRateArgs.add("--baseDN"); 925 authRateArgs.add("dc=example,dc=com"); 926 927 authRateArgs.add("--scope"); 928 authRateArgs.add("sub"); 929 930 authRateArgs.add("--filter"); 931 authRateArgs.add("(uid=user.1)"); 932 } 933 934 authRateArgs.add("--credentials"); 935 authRateArgs.add("password"); 936 937 authRateArgs.add("--numThreads"); 938 authRateArgs.add(String.valueOf(numThreadsArg.getValue())); 939 940 if (numIntervalsArg.isPresent()) 941 { 942 authRateArgs.add("--numIntervals"); 943 authRateArgs.add(String.valueOf(numIntervalsArg.getValue())); 944 } 945 946 if (intervalDurationSecondsArg.isPresent()) 947 { 948 authRateArgs.add("--intervalDuration"); 949 authRateArgs.add(String.valueOf( 950 intervalDurationSecondsArg.getValue())); 951 } 952 953 if (warmUpIntervalsArg.isPresent()) 954 { 955 authRateArgs.add("--warmUpIntervals"); 956 authRateArgs.add(String.valueOf(warmUpIntervalsArg.getValue())); 957 } 958 959 final String[] authRateArgsArray = 960 authRateArgs.toArray(StaticUtils.NO_STRINGS); 961 962 final AuthRate authRate = new AuthRate(getOut(), getErr()); 963 964 final ResultCode authRateResultCode = 965 authRate.runTool(authRateArgsArray); 966 if (authRateResultCode == ResultCode.SUCCESS) 967 { 968 final String message = "The authrate tool completed successfully."; 969 completionMessage.compareAndSet(null, message); 970 wrapOut(0, WRAP_COLUMN, message); 971 } 972 else 973 { 974 final String message = 975 "ERROR: The authrate tool exited with error result code " + 976 authRateResultCode + '.'; 977 completionMessage.compareAndSet(null, message); 978 wrapErr(0, WRAP_COLUMN, message); 979 } 980 981 return authRateResultCode; 982 } 983 984 985 986 /** 987 * Invokes the {@link SearchAndModRate} tool with an appropriate set of 988 * arguments. 989 * 990 * @param listenPort The port on which the LDAP listener is listening. 991 * 992 * @return The result code obtained from the {@code SearchAndModRate} tool. 993 */ 994 @NotNull() 995 private ResultCode invokeSearchAndModRate(final int listenPort) 996 { 997 final List<String> searchAndModRateArgs = new ArrayList<>(); 998 999 searchAndModRateArgs.add("--hostname"); 1000 searchAndModRateArgs.add("localhost"); 1001 1002 searchAndModRateArgs.add("--port"); 1003 searchAndModRateArgs.add(String.valueOf(listenPort)); 1004 1005 if (useSSLArg.isPresent()) 1006 { 1007 searchAndModRateArgs.add("--useSSL"); 1008 searchAndModRateArgs.add("--trustAll"); 1009 } 1010 1011 searchAndModRateArgs.add("--baseDN"); 1012 searchAndModRateArgs.add("dc=example,dc=com"); 1013 1014 searchAndModRateArgs.add("--scope"); 1015 searchAndModRateArgs.add("sub"); 1016 1017 searchAndModRateArgs.add("--filter"); 1018 searchAndModRateArgs.add("(objectClass=*)"); 1019 1020 searchAndModRateArgs.add("--modifyAttribute"); 1021 searchAndModRateArgs.add("description"); 1022 1023 searchAndModRateArgs.add("--valueLength"); 1024 searchAndModRateArgs.add("10"); 1025 1026 searchAndModRateArgs.add("--numThreads"); 1027 searchAndModRateArgs.add(String.valueOf(numThreadsArg.getValue())); 1028 1029 if (numIntervalsArg.isPresent()) 1030 { 1031 searchAndModRateArgs.add("--numIntervals"); 1032 searchAndModRateArgs.add(String.valueOf(numIntervalsArg.getValue())); 1033 } 1034 1035 if (intervalDurationSecondsArg.isPresent()) 1036 { 1037 searchAndModRateArgs.add("--intervalDuration"); 1038 searchAndModRateArgs.add(String.valueOf( 1039 intervalDurationSecondsArg.getValue())); 1040 } 1041 1042 if (warmUpIntervalsArg.isPresent()) 1043 { 1044 searchAndModRateArgs.add("--warmUpIntervals"); 1045 searchAndModRateArgs.add(String.valueOf(warmUpIntervalsArg.getValue())); 1046 } 1047 1048 final String[] searchAndModRateArgsArray = 1049 searchAndModRateArgs.toArray(StaticUtils.NO_STRINGS); 1050 1051 final SearchAndModRate searchAndModRate = 1052 new SearchAndModRate(getOut(), getErr()); 1053 1054 final ResultCode searchAndModRateResultCode = 1055 searchAndModRate.runTool(searchAndModRateArgsArray); 1056 if (searchAndModRateResultCode == ResultCode.SUCCESS) 1057 { 1058 final String message = 1059 "The search-and-mod-rate tool completed successfully."; 1060 completionMessage.compareAndSet(null, message); 1061 wrapOut(0, WRAP_COLUMN, message); 1062 } 1063 else 1064 { 1065 final String message = 1066 "ERROR: The search-and-mod-rate tool exited with error result " + 1067 "code " + searchAndModRateResultCode + '.'; 1068 completionMessage.compareAndSet(null, message); 1069 wrapErr(0, WRAP_COLUMN, message); 1070 } 1071 1072 return searchAndModRateResultCode; 1073 } 1074 1075 1076 1077 /** 1078 * Retrieves a set of information that may be used to generate example usage 1079 * information. Each element in the returned map should consist of a map 1080 * between an example set of arguments and a string that describes the 1081 * behavior of the tool when invoked with that set of arguments. 1082 * 1083 * @return A set of information that may be used to generate example usage 1084 * information. It may be {@code null} or empty if no example usage 1085 * information is available. 1086 */ 1087 @Override() 1088 @NotNull() 1089 public LinkedHashMap<String[],String> getExampleUsages() 1090 { 1091 final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(); 1092 1093 examples.put( 1094 new String[] 1095 { 1096 "--numThreads", "10" 1097 }, 1098 "Test LDAP SDK performance with the searchrate tool using ten " + 1099 "concurrent threads. Communication will use an insecure " + 1100 "connection, and each search will return a success result with " + 1101 "a single matching entry. The tool will continue to run until " + 1102 "it is interrupted."); 1103 1104 examples.put( 1105 new String[] 1106 { 1107 "--tool", "modrate", 1108 "--numThreads", "10", 1109 "--useSSL", 1110 "--resultCode", "32", 1111 "--diagnosticMessage", "The base entry does not exist", 1112 "--warmUpIntervals", "5", 1113 "--numIntervals", "10", 1114 "--intervalDurationSeconds", "5" 1115 }, 1116 "Test LDAP SDK performance with the modrate tool using ten " + 1117 "concurrent threads over SSL-encrypted connections. Each " + 1118 "modify will return an error result with a result code of 32 " + 1119 "(noSuchObject) and a diagnostic message of 'The target entry " + 1120 "does not exist'. The tool will run five warm-up intervals " + 1121 "of five seconds each, and then ten 5-second intervals in " + 1122 "which it will capture statistics. The tool will exit after " + 1123 "those last ten intervals have completed."); 1124 1125 return examples; 1126 } 1127}