001 /*
002 * Copyright 2009-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2009-2014 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk.examples;
022
023
024
025 import java.io.OutputStream;
026 import java.io.Serializable;
027 import java.text.ParseException;
028 import java.util.LinkedHashMap;
029 import java.util.LinkedHashSet;
030 import java.util.List;
031 import java.util.concurrent.CyclicBarrier;
032 import java.util.concurrent.atomic.AtomicLong;
033
034 import com.unboundid.ldap.sdk.LDAPConnection;
035 import com.unboundid.ldap.sdk.LDAPConnectionOptions;
036 import com.unboundid.ldap.sdk.LDAPException;
037 import com.unboundid.ldap.sdk.ResultCode;
038 import com.unboundid.ldap.sdk.SearchScope;
039 import com.unboundid.ldap.sdk.Version;
040 import com.unboundid.util.ColumnFormatter;
041 import com.unboundid.util.FixedRateBarrier;
042 import com.unboundid.util.FormattableColumn;
043 import com.unboundid.util.HorizontalAlignment;
044 import com.unboundid.util.LDAPCommandLineTool;
045 import com.unboundid.util.ObjectPair;
046 import com.unboundid.util.OutputFormat;
047 import com.unboundid.util.ResultCodeCounter;
048 import com.unboundid.util.ThreadSafety;
049 import com.unboundid.util.ThreadSafetyLevel;
050 import com.unboundid.util.ValuePattern;
051 import com.unboundid.util.args.ArgumentException;
052 import com.unboundid.util.args.ArgumentParser;
053 import com.unboundid.util.args.BooleanArgument;
054 import com.unboundid.util.args.IntegerArgument;
055 import com.unboundid.util.args.ScopeArgument;
056 import com.unboundid.util.args.StringArgument;
057
058 import static com.unboundid.util.StaticUtils.*;
059
060
061
062 /**
063 * This class provides a tool that can be used to test authentication processing
064 * in an LDAP directory server using multiple threads. Each authentication will
065 * consist of two operations: a search to find the target entry followed by a
066 * bind to verify the credentials for that user. The search will use the given
067 * base DN and filter, either or both of which may be a value pattern as
068 * described in the {@link ValuePattern} class. This makes it possible to
069 * search over a range of entries rather than repeatedly performing searches
070 * with the same base DN and filter.
071 * <BR><BR>
072 * Some of the APIs demonstrated by this example include:
073 * <UL>
074 * <LI>Argument Parsing (from the {@code com.unboundid.util.args}
075 * package)</LI>
076 * <LI>LDAP Command-Line Tool (from the {@code com.unboundid.util}
077 * package)</LI>
078 * <LI>LDAP Communication (from the {@code com.unboundid.ldap.sdk}
079 * package)</LI>
080 * <LI>Value Patterns (from the {@code com.unboundid.util} package)</LI>
081 * </UL>
082 * Each search must match exactly one entry, and this tool will then attempt to
083 * authenticate as the user associated with that entry. It supports simple
084 * authentication, as well as the CRAM-MD5, DIGEST-MD5, and PLAIN SASL
085 * mechanisms.
086 * <BR><BR>
087 * All of the necessary information is provided using command line arguments.
088 * Supported arguments include those allowed by the {@link LDAPCommandLineTool}
089 * class, as well as the following additional arguments:
090 * <UL>
091 * <LI>"-b {baseDN}" or "--baseDN {baseDN}" -- specifies the base DN to use
092 * for the searches. This must be provided. It may be a simple DN, or it
093 * may be a value pattern to express a range of base DNs.</LI>
094 * <LI>"-s {scope}" or "--scope {scope}" -- specifies the scope to use for the
095 * search. The scope value should be one of "base", "one", "sub", or
096 * "subord". If this isn't specified, then a scope of "sub" will be
097 * used.</LI>
098 * <LI>"-f {filter}" or "--filter {filter}" -- specifies the filter to use for
099 * the searches. This must be provided. It may be a simple filter, or it
100 * may be a value pattern to express a range of filters.</LI>
101 * <LI>"-A {name}" or "--attribute {name}" -- specifies the name of an
102 * attribute that should be included in entries returned from the server.
103 * If this is not provided, then all user attributes will be requested.
104 * This may include special tokens that the server may interpret, like
105 * "1.1" to indicate that no attributes should be returned, "*", for all
106 * user attributes, or "+" for all operational attributes. Multiple
107 * attributes may be requested with multiple instances of this
108 * argument.</LI>
109 * <LI>"-C {password}" or "--credentials {password}" -- specifies the password
110 * to use when authenticating users identified by the searches.</LI>
111 * <LI>"-a {authType}" or "--authType {authType}" -- specifies the type of
112 * authentication to attempt. Supported values include "SIMPLE",
113 * "CRAM-MD5", "DIGEST-MD5", and "PLAIN".
114 * <LI>"-t {num}" or "--numThreads {num}" -- specifies the number of
115 * concurrent threads to use when performing the authentication
116 * processing. If this is not provided, then a default of one thread will
117 * be used.</LI>
118 * <LI>"-i {sec}" or "--intervalDuration {sec}" -- specifies the length of
119 * time in seconds between lines out output. If this is not provided,
120 * then a default interval duration of five seconds will be used.</LI>
121 * <LI>"-I {num}" or "--numIntervals {num}" -- specifies the maximum number of
122 * intervals for which to run. If this is not provided, then it will
123 * run forever.</LI>
124 * <LI>"-r {auths-per-second}" or "--ratePerSecond {auths-per-second}" --
125 * specifies the target number of authorizations to perform per second.
126 * It is still necessary to specify a sufficient number of threads for
127 * achieving this rate. If this option is not provided, then the tool
128 * will run at the maximum rate for the specified number of threads.</LI>
129 * <LI>"--warmUpIntervals {num}" -- specifies the number of intervals to
130 * complete before beginning overall statistics collection.</LI>
131 * <LI>"--timestampFormat {format}" -- specifies the format to use for
132 * timestamps included before each output line. The format may be one of
133 * "none" (for no timestamps), "with-date" (to include both the date and
134 * the time), or "without-date" (to include only time time).</LI>
135 * <LI>"--suppressErrorResultCodes" -- Indicates that information about the
136 * result codes for failed operations should not be displayed.</LI>
137 * <LI>"-c" or "--csv" -- Generate output in CSV format rather than a
138 * display-friendly format.</LI>
139 * </UL>
140 */
141 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
142 public final class AuthRate
143 extends LDAPCommandLineTool
144 implements Serializable
145 {
146 /**
147 * The serial version UID for this serializable class.
148 */
149 private static final long serialVersionUID = 6918029871717330547L;
150
151
152
153 // The argument used to indicate whether to generate output in CSV format.
154 private BooleanArgument csvFormat;
155
156 // The argument used to indicate whether to suppress information about error
157 // result codes.
158 private BooleanArgument suppressErrorsArgument;
159
160 // The argument used to specify the collection interval.
161 private IntegerArgument collectionInterval;
162
163 // The argument used to specify the number of intervals.
164 private IntegerArgument numIntervals;
165
166 // The argument used to specify the number of threads.
167 private IntegerArgument numThreads;
168
169 // The argument used to specify the seed to use for the random number
170 // generator.
171 private IntegerArgument randomSeed;
172
173 // The target rate of auths per second.
174 private IntegerArgument ratePerSecond;
175
176 // The number of warm-up intervals to perform.
177 private IntegerArgument warmUpIntervals;
178
179 // The argument used to specify the attributes to return.
180 private StringArgument attributes;
181
182 // The argument used to specify the type of authentication to perform.
183 private StringArgument authType;
184
185 // The argument used to specify the base DNs for the searches.
186 private StringArgument baseDN;
187
188 // The argument used to specify the filters for the searches.
189 private StringArgument filter;
190
191 // The argument used to specify the scope for the searches.
192 private ScopeArgument scopeArg;
193
194 // The argument used to specify the timestamp format.
195 private StringArgument timestampFormat;
196
197 // The argument used to specify the password to use to authenticate.
198 private StringArgument userPassword;
199
200
201
202 /**
203 * Parse the provided command line arguments and make the appropriate set of
204 * changes.
205 *
206 * @param args The command line arguments provided to this program.
207 */
208 public static void main(final String[] args)
209 {
210 final ResultCode resultCode = main(args, System.out, System.err);
211 if (resultCode != ResultCode.SUCCESS)
212 {
213 System.exit(resultCode.intValue());
214 }
215 }
216
217
218
219 /**
220 * Parse the provided command line arguments and make the appropriate set of
221 * changes.
222 *
223 * @param args The command line arguments provided to this program.
224 * @param outStream The output stream to which standard out should be
225 * written. It may be {@code null} if output should be
226 * suppressed.
227 * @param errStream The output stream to which standard error should be
228 * written. It may be {@code null} if error messages
229 * should be suppressed.
230 *
231 * @return A result code indicating whether the processing was successful.
232 */
233 public static ResultCode main(final String[] args,
234 final OutputStream outStream,
235 final OutputStream errStream)
236 {
237 final AuthRate authRate = new AuthRate(outStream, errStream);
238 return authRate.runTool(args);
239 }
240
241
242
243 /**
244 * Creates a new instance of this tool.
245 *
246 * @param outStream The output stream to which standard out should be
247 * written. It may be {@code null} if output should be
248 * suppressed.
249 * @param errStream The output stream to which standard error should be
250 * written. It may be {@code null} if error messages
251 * should be suppressed.
252 */
253 public AuthRate(final OutputStream outStream, final OutputStream errStream)
254 {
255 super(outStream, errStream);
256 }
257
258
259
260 /**
261 * Retrieves the name for this tool.
262 *
263 * @return The name for this tool.
264 */
265 @Override()
266 public String getToolName()
267 {
268 return "authrate";
269 }
270
271
272
273 /**
274 * Retrieves the description for this tool.
275 *
276 * @return The description for this tool.
277 */
278 @Override()
279 public String getToolDescription()
280 {
281 return "Perform repeated authentications against an LDAP directory " +
282 "server, where each authentication consists of a search to " +
283 "find a user followed by a bind to verify the credentials " +
284 "for that user.";
285 }
286
287
288
289 /**
290 * Retrieves the version string for this tool.
291 *
292 * @return The version string for this tool.
293 */
294 @Override()
295 public String getToolVersion()
296 {
297 return Version.NUMERIC_VERSION_STRING;
298 }
299
300
301
302 /**
303 * Adds the arguments used by this program that aren't already provided by the
304 * generic {@code LDAPCommandLineTool} framework.
305 *
306 * @param parser The argument parser to which the arguments should be added.
307 *
308 * @throws ArgumentException If a problem occurs while adding the arguments.
309 */
310 @Override()
311 public void addNonLDAPArguments(final ArgumentParser parser)
312 throws ArgumentException
313 {
314 String description = "The base DN to use for the searches. It may be a " +
315 "simple DN or a value pattern to specify a range of DNs (e.g., " +
316 "\"uid=user.[1-1000],ou=People,dc=example,dc=com\"). This must be " +
317 "provided.";
318 baseDN = new StringArgument('b', "baseDN", true, 1, "{dn}", description);
319 parser.addArgument(baseDN);
320
321
322 description = "The scope to use for the searches. It should be 'base', " +
323 "'one', 'sub', or 'subord'. If this is not provided, a " +
324 "default scope of 'sub' will be used.";
325 scopeArg = new ScopeArgument('s', "scope", false, "{scope}", description,
326 SearchScope.SUB);
327 parser.addArgument(scopeArg);
328
329
330 description = "The filter to use for the searches. It may be a simple " +
331 "filter or a value pattern to specify a range of filters " +
332 "(e.g., \"(uid=user.[1-1000])\"). This must be provided.";
333 filter = new StringArgument('f', "filter", true, 1, "{filter}",
334 description);
335 parser.addArgument(filter);
336
337
338 description = "The name of an attribute to include in entries returned " +
339 "from the searches. Multiple attributes may be requested " +
340 "by providing this argument multiple times. If no return " +
341 "attributes are specified, then entries will be returned " +
342 "with all user attributes.";
343 attributes = new StringArgument('A', "attribute", false, 0, "{name}",
344 description);
345 parser.addArgument(attributes);
346
347
348 description = "The password to use when binding as the users returned " +
349 "from the searches. This must be provided.";
350 userPassword = new StringArgument('C', "credentials", true, 1, "{password}",
351 description);
352 parser.addArgument(userPassword);
353
354
355 description = "The type of authentication to perform. Allowed values " +
356 "are: SIMPLE, CRAM-MD5, DIGEST-MD5, and PLAIN. If no "+
357 "value is provided, then SIMPLE authentication will be " +
358 "performed.";
359 final LinkedHashSet<String> allowedAuthTypes = new LinkedHashSet<String>(4);
360 allowedAuthTypes.add("simple");
361 allowedAuthTypes.add("cram-md5");
362 allowedAuthTypes.add("digest-md5");
363 allowedAuthTypes.add("plain");
364 authType = new StringArgument('a', "authType", true, 1, "{authType}",
365 description, allowedAuthTypes, "simple");
366 parser.addArgument(authType);
367
368
369 description = "The number of threads to use to perform the " +
370 "authentication processing. If this is not provided, then " +
371 "a default of one thread will be used.";
372 numThreads = new IntegerArgument('t', "numThreads", true, 1, "{num}",
373 description, 1, Integer.MAX_VALUE, 1);
374 parser.addArgument(numThreads);
375
376
377 description = "The length of time in seconds between output lines. If " +
378 "this is not provided, then a default interval of five " +
379 "seconds will be used.";
380 collectionInterval = new IntegerArgument('i', "intervalDuration", true, 1,
381 "{num}", description, 1,
382 Integer.MAX_VALUE, 5);
383 parser.addArgument(collectionInterval);
384
385
386 description = "The maximum number of intervals for which to run. If " +
387 "this is not provided, then the tool will run until it is " +
388 "interrupted.";
389 numIntervals = new IntegerArgument('I', "numIntervals", true, 1, "{num}",
390 description, 1, Integer.MAX_VALUE,
391 Integer.MAX_VALUE);
392 parser.addArgument(numIntervals);
393
394 description = "The target number of authorizations to perform per " +
395 "second. It is still necessary to specify a sufficient " +
396 "number of threads for achieving this rate. If this " +
397 "option is not provided, then the tool will run at the " +
398 "maximum rate for the specified number of threads.";
399 ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1,
400 "{auths-per-second}", description,
401 1, Integer.MAX_VALUE);
402 parser.addArgument(ratePerSecond);
403
404 description = "The number of intervals to complete before beginning " +
405 "overall statistics collection. Specifying a nonzero " +
406 "number of warm-up intervals gives the client and server " +
407 "a chance to warm up without skewing performance results.";
408 warmUpIntervals = new IntegerArgument(null, "warmUpIntervals", true, 1,
409 "{num}", description, 0, Integer.MAX_VALUE, 0);
410 parser.addArgument(warmUpIntervals);
411
412 description = "Indicates the format to use for timestamps included in " +
413 "the output. A value of 'none' indicates that no " +
414 "timestamps should be included. A value of 'with-date' " +
415 "indicates that both the date and the time should be " +
416 "included. A value of 'without-date' indicates that only " +
417 "the time should be included.";
418 final LinkedHashSet<String> allowedFormats = new LinkedHashSet<String>(3);
419 allowedFormats.add("none");
420 allowedFormats.add("with-date");
421 allowedFormats.add("without-date");
422 timestampFormat = new StringArgument(null, "timestampFormat", true, 1,
423 "{format}", description, allowedFormats, "none");
424 parser.addArgument(timestampFormat);
425
426 description = "Indicates that information about the result codes for " +
427 "failed operations should not be displayed.";
428 suppressErrorsArgument = new BooleanArgument(null,
429 "suppressErrorResultCodes", 1, description);
430 parser.addArgument(suppressErrorsArgument);
431
432 description = "Generate output in CSV format rather than a " +
433 "display-friendly format";
434 csvFormat = new BooleanArgument('c', "csv", 1, description);
435 parser.addArgument(csvFormat);
436
437 description = "Specifies the seed to use for the random number generator.";
438 randomSeed = new IntegerArgument('R', "randomSeed", false, 1, "{value}",
439 description);
440 parser.addArgument(randomSeed);
441 }
442
443
444
445 /**
446 * Indicates whether this tool supports creating connections to multiple
447 * servers. If it is to support multiple servers, then the "--hostname" and
448 * "--port" arguments will be allowed to be provided multiple times, and
449 * will be required to be provided the same number of times. The same type of
450 * communication security and bind credentials will be used for all servers.
451 *
452 * @return {@code true} if this tool supports creating connections to
453 * multiple servers, or {@code false} if not.
454 */
455 @Override()
456 protected boolean supportsMultipleServers()
457 {
458 return true;
459 }
460
461
462
463 /**
464 * Retrieves the connection options that should be used for connections
465 * created for use with this tool.
466 *
467 * @return The connection options that should be used for connections created
468 * for use with this tool.
469 */
470 @Override()
471 public LDAPConnectionOptions getConnectionOptions()
472 {
473 final LDAPConnectionOptions options = new LDAPConnectionOptions();
474 options.setAutoReconnect(true);
475 options.setUseSynchronousMode(true);
476 return options;
477 }
478
479
480
481 /**
482 * Performs the actual processing for this tool. In this case, it gets a
483 * connection to the directory server and uses it to perform the requested
484 * searches.
485 *
486 * @return The result code for the processing that was performed.
487 */
488 @Override()
489 public ResultCode doToolProcessing()
490 {
491 // Determine the random seed to use.
492 final Long seed;
493 if (randomSeed.isPresent())
494 {
495 seed = Long.valueOf(randomSeed.getValue());
496 }
497 else
498 {
499 seed = null;
500 }
501
502 // Create value patterns for the base DN and filter.
503 final ValuePattern dnPattern;
504 try
505 {
506 dnPattern = new ValuePattern(baseDN.getValue(), seed);
507 }
508 catch (ParseException pe)
509 {
510 err("Unable to parse the base DN value pattern: ", pe.getMessage());
511 return ResultCode.PARAM_ERROR;
512 }
513
514 final ValuePattern filterPattern;
515 try
516 {
517 filterPattern = new ValuePattern(filter.getValue(), seed);
518 }
519 catch (ParseException pe)
520 {
521 err("Unable to parse the filter pattern: ", pe.getMessage());
522 return ResultCode.PARAM_ERROR;
523 }
524
525
526 // Get the attributes to return.
527 final String[] attrs;
528 if (attributes.isPresent())
529 {
530 final List<String> attrList = attributes.getValues();
531 attrs = new String[attrList.size()];
532 attrList.toArray(attrs);
533 }
534 else
535 {
536 attrs = NO_STRINGS;
537 }
538
539
540 // If the --ratePerSecond option was specified, then limit the rate
541 // accordingly.
542 FixedRateBarrier fixedRateBarrier = null;
543 if (ratePerSecond.isPresent())
544 {
545 final int intervalSeconds = collectionInterval.getValue();
546 final int ratePerInterval = ratePerSecond.getValue() * intervalSeconds;
547
548 fixedRateBarrier =
549 new FixedRateBarrier(1000L * intervalSeconds, ratePerInterval);
550 }
551
552
553 // Determine whether to include timestamps in the output and if so what
554 // format should be used for them.
555 final boolean includeTimestamp;
556 final String timeFormat;
557 if (timestampFormat.getValue().equalsIgnoreCase("with-date"))
558 {
559 includeTimestamp = true;
560 timeFormat = "dd/MM/yyyy HH:mm:ss";
561 }
562 else if (timestampFormat.getValue().equalsIgnoreCase("without-date"))
563 {
564 includeTimestamp = true;
565 timeFormat = "HH:mm:ss";
566 }
567 else
568 {
569 includeTimestamp = false;
570 timeFormat = null;
571 }
572
573
574 // Determine whether any warm-up intervals should be run.
575 final long totalIntervals;
576 final boolean warmUp;
577 int remainingWarmUpIntervals = warmUpIntervals.getValue();
578 if (remainingWarmUpIntervals > 0)
579 {
580 warmUp = true;
581 totalIntervals = 0L + numIntervals.getValue() + remainingWarmUpIntervals;
582 }
583 else
584 {
585 warmUp = true;
586 totalIntervals = 0L + numIntervals.getValue();
587 }
588
589
590 // Create the table that will be used to format the output.
591 final OutputFormat outputFormat;
592 if (csvFormat.isPresent())
593 {
594 outputFormat = OutputFormat.CSV;
595 }
596 else
597 {
598 outputFormat = OutputFormat.COLUMNS;
599 }
600
601 final ColumnFormatter formatter = new ColumnFormatter(includeTimestamp,
602 timeFormat, outputFormat, " ",
603 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent",
604 "Auths/Sec"),
605 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent",
606 "Avg Dur ms"),
607 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Recent",
608 "Errors/Sec"),
609 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall",
610 "Auths/Sec"),
611 new FormattableColumn(12, HorizontalAlignment.RIGHT, "Overall",
612 "Avg Dur ms"));
613
614
615 // Create values to use for statistics collection.
616 final AtomicLong authCounter = new AtomicLong(0L);
617 final AtomicLong errorCounter = new AtomicLong(0L);
618 final AtomicLong authDurations = new AtomicLong(0L);
619 final ResultCodeCounter rcCounter = new ResultCodeCounter();
620
621
622 // Determine the length of each interval in milliseconds.
623 final long intervalMillis = 1000L * collectionInterval.getValue();
624
625
626 // Create the threads to use for the searches.
627 final CyclicBarrier barrier = new CyclicBarrier(numThreads.getValue() + 1);
628 final AuthRateThread[] threads = new AuthRateThread[numThreads.getValue()];
629 for (int i=0; i < threads.length; i++)
630 {
631 final LDAPConnection searchConnection;
632 final LDAPConnection bindConnection;
633 try
634 {
635 searchConnection = getConnection();
636 bindConnection = getConnection();
637 }
638 catch (LDAPException le)
639 {
640 err("Unable to connect to the directory server: ",
641 getExceptionMessage(le));
642 return le.getResultCode();
643 }
644
645 threads[i] = new AuthRateThread(this, i, searchConnection, bindConnection,
646 dnPattern, scopeArg.getValue(), filterPattern, attrs,
647 userPassword.getValue(), authType.getValue(), barrier, authCounter,
648 authDurations, errorCounter, rcCounter, fixedRateBarrier);
649 threads[i].start();
650 }
651
652
653 // Display the table header.
654 for (final String headerLine : formatter.getHeaderLines(true))
655 {
656 out(headerLine);
657 }
658
659
660 // Indicate that the threads can start running.
661 try
662 {
663 barrier.await();
664 } catch (Exception e) {}
665 long overallStartTime = System.nanoTime();
666 long nextIntervalStartTime = System.currentTimeMillis() + intervalMillis;
667
668
669 boolean setOverallStartTime = false;
670 long lastDuration = 0L;
671 long lastNumErrors = 0L;
672 long lastNumAuths = 0L;
673 long lastEndTime = System.nanoTime();
674 for (long i=0; i < totalIntervals; i++)
675 {
676 final long startTimeMillis = System.currentTimeMillis();
677 final long sleepTimeMillis = nextIntervalStartTime - startTimeMillis;
678 nextIntervalStartTime += intervalMillis;
679 try
680 {
681 if (sleepTimeMillis > 0)
682 {
683 Thread.sleep(sleepTimeMillis);
684 }
685 } catch (Exception e) {}
686
687 final long endTime = System.nanoTime();
688 final long intervalDuration = endTime - lastEndTime;
689
690 final long numAuths;
691 final long numErrors;
692 final long totalDuration;
693 if (warmUp && (remainingWarmUpIntervals > 0))
694 {
695 numAuths = authCounter.getAndSet(0L);
696 numErrors = errorCounter.getAndSet(0L);
697 totalDuration = authDurations.getAndSet(0L);
698 }
699 else
700 {
701 numAuths = authCounter.get();
702 numErrors = errorCounter.get();
703 totalDuration = authDurations.get();
704 }
705
706 final long recentNumAuths = numAuths - lastNumAuths;
707 final long recentNumErrors = numErrors - lastNumErrors;
708 final long recentDuration = totalDuration - lastDuration;
709
710 final double numSeconds = intervalDuration / 1000000000.0d;
711 final double recentAuthRate = recentNumAuths / numSeconds;
712 final double recentErrorRate = recentNumErrors / numSeconds;
713
714 final double recentAvgDuration;
715 if (recentNumAuths > 0L)
716 {
717 recentAvgDuration = 1.0d * recentDuration / recentNumAuths / 1000000;
718 }
719 else
720 {
721 recentAvgDuration = 0.0d;
722 }
723
724 if (warmUp && (remainingWarmUpIntervals > 0))
725 {
726 out(formatter.formatRow(recentAuthRate, recentAvgDuration,
727 recentErrorRate, "warming up", "warming up"));
728
729 remainingWarmUpIntervals--;
730 if (remainingWarmUpIntervals == 0)
731 {
732 out("Warm-up completed. Beginning overall statistics collection.");
733 setOverallStartTime = true;
734 }
735 }
736 else
737 {
738 if (setOverallStartTime)
739 {
740 overallStartTime = lastEndTime;
741 setOverallStartTime = false;
742 }
743
744 final double numOverallSeconds =
745 (endTime - overallStartTime) / 1000000000.0d;
746 final double overallAuthRate = numAuths / numOverallSeconds;
747
748 final double overallAvgDuration;
749 if (numAuths > 0L)
750 {
751 overallAvgDuration = 1.0d * totalDuration / numAuths / 1000000;
752 }
753 else
754 {
755 overallAvgDuration = 0.0d;
756 }
757
758 out(formatter.formatRow(recentAuthRate, recentAvgDuration,
759 recentErrorRate, overallAuthRate, overallAvgDuration));
760
761 lastNumAuths = numAuths;
762 lastNumErrors = numErrors;
763 lastDuration = totalDuration;
764 }
765
766 final List<ObjectPair<ResultCode,Long>> rcCounts =
767 rcCounter.getCounts(true);
768 if ((! suppressErrorsArgument.isPresent()) && (! rcCounts.isEmpty()))
769 {
770 err("\tError Results:");
771 for (final ObjectPair<ResultCode,Long> p : rcCounts)
772 {
773 err("\t", p.getFirst().getName(), ": ", p.getSecond());
774 }
775 }
776
777 lastEndTime = endTime;
778 }
779
780
781 // Stop all of the threads.
782 ResultCode resultCode = ResultCode.SUCCESS;
783 for (final AuthRateThread t : threads)
784 {
785 final ResultCode r = t.stopRunning();
786 if (resultCode == ResultCode.SUCCESS)
787 {
788 resultCode = r;
789 }
790 }
791
792 return resultCode;
793 }
794
795
796
797 /**
798 * {@inheritDoc}
799 */
800 @Override()
801 public LinkedHashMap<String[],String> getExampleUsages()
802 {
803 final LinkedHashMap<String[],String> examples =
804 new LinkedHashMap<String[],String>(1);
805
806 final String[] args =
807 {
808 "--hostname", "server.example.com",
809 "--port", "389",
810 "--bindDN", "uid=admin,dc=example,dc=com",
811 "--bindPassword", "password",
812 "--baseDN", "dc=example,dc=com",
813 "--scope", "sub",
814 "--filter", "(uid=user.[1-1000000])",
815 "--credentials", "password",
816 "--numThreads", "10"
817 };
818 final String description =
819 "Test authentication performance by searching randomly across a set " +
820 "of one million users located below 'dc=example,dc=com' with ten " +
821 "concurrent threads and performing simple binds with a password of " +
822 "'password'. The searches will be performed anonymously.";
823 examples.put(args, description);
824
825 return examples;
826 }
827 }