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