mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Gaetan Boismal
23.40.2014 85eed99dc29d3d7c557db102eea9d0441a9c665c
OPENDJ-1074 (CR-4030) Implement combined add/del rate tool
Adding an add/del rate tool named addrate.
-opendj-core
** EntryGenerator.java:
* Changing the subtemplate parsing behavior, add the "generated branches" options
** TemplateFile.java
* Adding the "generated branches" options
** addrate.template
* New template used to generate entries

-opendj-ldap-toolkit
** addrate (bat and bin)
* Adding new tool scripts for unix and windows
** AddRate.java
* addrate tool new class
** PerformanceRunner.java
* Using parameter object in constructor, adding some messages localization
** PerformanceRunnerOptions.java
* Helps to simplify the way to initialize a PerformanceRunner instance
** MakeLDIF.java
* Little refactoring in order to allow the addrate tool to use some make-ldif parameters
** tools.properties
* Adding addrate tool message and also some new generic messages
** Minor changes (constructor) in other tools and tests files
6 files added
11 files modified
1050 ■■■■ changed files
opendj-core/src/main/java/org/forgerock/opendj/ldif/EntryGenerator.java 22 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateFile.java 86 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template 32 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java 2 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/assembly/bat/addrate 33 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/assembly/bin/addrate 36 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/AddRate.java 429 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/AuthRate.java 10 ●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/MakeLDIF.java 45 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/ModRate.java 6 ●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java 86 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunnerOptions.java 90 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/SearchRate.java 6 ●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/resources/com/forgerock/opendj/ldap/tools/tools.properties 45 ●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/test/java/com/forgerock/opendj/ldap/tools/AddRateITCase.java 115 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/test/java/com/forgerock/opendj/ldap/tools/MakeLDIFTestCase.java 2 ●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/test/java/com/forgerock/opendj/ldap/tools/ToolsITCase.java 5 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldif/EntryGenerator.java
@@ -111,6 +111,12 @@
     */
    private InputStream templateStream;
    /** Indicates whether branch entries should be generated.
     *
     *  Default is {@code true}
     */
    private boolean generateBranches = true;
    /** Dictionary of constants to use in the template file. */
    private Map<String, String> constants = new HashMap<String, String>();
@@ -222,6 +228,20 @@
    }
    /**
     * Sets the flag which indicates whether branch entries should be generated.
     *
     * The default is {@code true}.
     *
     * @param generateBranches
     *              Indicates whether or not the branches DN entries has to be generated.
     * @return A reference to this {@code EntryGenerator}.
     */
    public EntryGenerator setGenerateBranches(boolean generateBranches) {
        this.generateBranches = generateBranches;
        return this;
    }
    /**
     * Checks if there are some warning(s) after parsing the template file.
     * <p>
     * Warnings are available only after the first call to {@code hasNext()} or
@@ -286,7 +306,7 @@
        if (schema == null) {
            schema = Schema.getDefaultSchema();
        }
        templateFile = new TemplateFile(schema, constants, resourcePath, new Random(randomSeed));
        templateFile = new TemplateFile(schema, constants, resourcePath, new Random(randomSeed), generateBranches);
        try {
            if (templatePath != null) {
                templateFile.parse(templatePath, warnings);
opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateFile.java
@@ -104,6 +104,9 @@
    /** The name of the file holding the list of last names. */
    private static final String LAST_NAME_FILE = "last.names";
    /** Default value for infinite number of entries. */
    private static final int INFINITE_ENTRIES = -1;
    /**
     * A map of the contents of various text files used during the parsing
     * process, mapped from absolute path to the array of lines in the file.
@@ -149,6 +152,9 @@
    /** The next last name that should be used. */
    private String lastName;
    /** Indicates whether branch entries should be generated. */
    private boolean generateBranches;
    /**
     * The resource path to use for filesystem elements that cannot be found
     * anywhere else.
@@ -180,7 +186,7 @@
     *             if a problem occurs when initializing
     */
    TemplateFile(Schema schema, Map<String, String> constants, String resourcePath) throws IOException {
        this(schema, constants, resourcePath, new Random());
        this(schema, constants, resourcePath, new Random(), true);
    }
    /**
@@ -197,12 +203,16 @@
     *            {@code null}.
     * @param random
     *            The random number generator for this template file.
     * @param generateBranches
     *            Indicates whether branch entries should be generated.
     * @throws IOException
     *             if a problem occurs when initializing
     */
    TemplateFile(Schema schema, Map<String, String> constants, String resourcePath, Random random)
    TemplateFile(Schema schema, Map<String, String> constants, String resourcePath,
                    Random random, boolean generateBranches)
            throws IOException {
        Reject.ifNull(schema, random);
        this.generateBranches = generateBranches;
        this.schema = schema;
        this.constants = constants != null ? constants : new HashMap<String, String>();
        this.resourcePath = resourcePath;
@@ -466,7 +476,7 @@
        // Finalize the branch and template definitions
        // and then update the template file variables.
        for (Branch b : templateData.branches.values()) {
            b.completeBranchInitialization(templateData.templates);
            b.completeBranchInitialization(templateData.templates, generateBranches);
        }
        for (Template t : templateData.templates.values()) {
@@ -835,30 +845,31 @@
    private Pair<String, Integer> parseSubordinateTemplate(final int lineNumber, final String line,
            final Element element, final String elementName, final List<LocalizableMessage> warnings)
            throws DecodeException {
        // It's a subordinate template, so we'll want to parse
        // the template name and the number of entries.
        // It's a subordinate template, so we'll want to parse the template name
        // and the number of entries if it is provided.
        final int colonPos = line.indexOf(':', SUBORDINATE_TEMPLATE_LABEL.length());
        final String templateName;
        int numEntries = INFINITE_ENTRIES;
        if (colonPos <= SUBORDINATE_TEMPLATE_LABEL.length()) {
            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_SUBORDINATE_TEMPLATE_NO_COLON.get(
                    lineNumber, element.getLabel(), elementName));
        }
            //No number of entries provided, generator will provides an infinite number of entries
            templateName = line.substring(SUBORDINATE_TEMPLATE_LABEL.length(), line.length()).trim();
        } else {
            templateName = line.substring(SUBORDINATE_TEMPLATE_LABEL.length(), colonPos).trim();
        final String templateName = line.substring(SUBORDINATE_TEMPLATE_LABEL.length(), colonPos).trim();
        try {
            final int numEntries = Integer.parseInt(line.substring(colonPos + 1).trim());
            if (numEntries < 0) {
                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_SUBORDINATE_INVALID_NUM_ENTRIES.get(
                        lineNumber, element.getLabel(), elementName, numEntries, templateName));
            } else if (numEntries == 0) {
                warnings.add(WARN_ENTRY_GENERATOR_SUBORDINATE_ZERO_ENTRIES.get(
                        lineNumber, element.getLabel(), elementName, templateName));
            try {
                numEntries = Integer.parseInt(line.substring(colonPos + 1).trim());
                if (numEntries == 0) {
                    warnings.add(WARN_ENTRY_GENERATOR_SUBORDINATE_ZERO_ENTRIES.get(
                            lineNumber, element.getLabel(), elementName, templateName));
                }
            } catch (NumberFormatException nfe) {
                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_SUBORDINATE_CANT_PARSE_NUMENTRIES.get(
                        templateName, lineNumber, element.getLabel(), elementName));
            }
            return Pair.of(templateName, numEntries);
        } catch (NumberFormatException nfe) {
            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_SUBORDINATE_CANT_PARSE_NUMENTRIES.get(
                    templateName, lineNumber, element.getLabel(), elementName));
        }
        return Pair.of(templateName, numEntries);
    }
    private static final int PARSING_STATIC_TEXT = 0;
@@ -1417,14 +1428,9 @@
         * initialization is completed. In particular, it should make sure that
         * all referenced subordinate templates actually exist in the template
         * file.
         *
         * @param templates
         *            The set of templates defined in the template file.
         * @throws DecodeException
         *             If any of the subordinate templates are not defined in
         *             the template file.
         */
        private void completeBranchInitialization(final Map<String, Template> templates) throws DecodeException {
        private void completeBranchInitialization(final Map<String, Template> templates,
                            boolean generateBranches) throws DecodeException {
            subordinateTemplates = new ArrayList<Template>();
            for (int i = 0; i < subordinateTemplateNames.size(); i++) {
                subordinateTemplates.add(templates.get(subordinateTemplateNames.get(i).toLowerCase()));
@@ -1434,7 +1440,7 @@
                }
            }
            nextEntry = buildBranchEntry();
            nextEntry = buildBranchEntry(generateBranches);
        }
        DN getBranchDN() {
@@ -1491,10 +1497,8 @@
        /**
         * Returns the entry corresponding to this branch.
         *
         * @return the entry, or null if it can't be generated
         */
        private TemplateEntry buildBranchEntry() {
        private TemplateEntry buildBranchEntry(boolean generateBranches) {
            final TemplateEntry entry = new TemplateEntry(this);
            final List<TemplateLine> lines = new ArrayList<TemplateLine>(rdnLines);
            lines.addAll(extraLines);
@@ -1504,6 +1508,11 @@
            for (int i = 0; i < subordinateTemplates.size(); i++) {
                subordinateTemplates.get(i).reset(entry.getDN(), numEntriesPerTemplate.get(i));
            }
            if (!generateBranches) {
                return null;
            }
            return entry;
        }
@@ -1685,7 +1694,10 @@
        /** parent DN of entries to generate for this template. */
        private DN parentDN;
        /** Number of entries to generate for this template. */
        /**
         * Number of entries to generate for this template.
         * Negative number means infinite generation.
         */
        private int numberOfEntries;
        /** Current count of generated entries for this template. */
@@ -1747,7 +1759,7 @@
            if (nextEntry != null) {
                return true;
            }
            while (entriesCount < numberOfEntries) {
            while ((entriesCount < numberOfEntries) || generateForever()) {
                // get the template entry
                if (!currentEntryIsInitialized) {
                    nextEntry = buildTemplateEntry();
@@ -1773,6 +1785,10 @@
            return false;
        }
        private boolean generateForever() {
            return numberOfEntries < 0;
        }
        /**
         * Returns the next generated entry.
         *
opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template
New file
@@ -0,0 +1,32 @@
define suffix=dc=example,dc=com
define maildomain=example.com
branch: [suffix]
branch: ou=People,[suffix]
subordinateTemplate: person
template: person
rdnAttr: uid
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
givenName: <first>
sn: <last>
cn: {givenName} {sn}
initials: {givenName:1}<random:chars:ABCDEFGHIJKLMNOPQRSTUVWXYZ:1>{sn:1}
employeeNumber: <sequential:0>
uid: user.{employeeNumber}
mail: {uid}@[maildomain]
userPassword: password
telephoneNumber: <random:telephone>
homePhone: <random:telephone>
pager: <random:telephone>
mobile: <random:telephone>
street: <random:numeric:5> <file:streets> Street
l: <file:cities>
st: <file:states>
postalCode: <random:numeric:5>
postalAddress: {cn}${street}${l}, {st}  {postalCode}
description: This is the description for {cn}.
opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
@@ -410,6 +410,8 @@
            // Remove this from the map.
            entryMap.remove(dn);
            requestsInProgress.remove(context);
            result = Responses.newResult(ResultCode.SUCCESS);
            handler.handleResult(result);
        }
        public <R extends ExtendedResult> void handleExtendedRequest(final Integer context,
opendj-ldap-toolkit/src/main/assembly/bat/addrate
New file
@@ -0,0 +1,33 @@
@echo off
rem CDDL HEADER START
rem
rem The contents of this file are subject to the terms of the
rem Common Development and Distribution License, Version 1.0 only
rem (the "License").  You may not use this file except in compliance
rem with the License.
rem
rem You can obtain a copy of the license at
rem legal-notices/CDDLv1_0.txt
rem or http://forgerock.org/license/CDDLv1.0.html.
rem See the License for the specific language governing permissions
rem and limitations under the License.
rem
rem When distributing Covered Code, include this CDDL HEADER in each
rem file and include the License file at legal-notices/CDDLv1_0.txt.
rem legal-notices/CDDLv1_0.txt.  If applicable,
rem add the following below this CDDL HEADER, with the fields enclosed
rem by brackets "[]" replaced with your own identifying information:
rem      Portions Copyright [yyyy] [name of copyright owner]
rem
rem CDDL HEADER END
rem
rem
rem      Copyright 2014 ForgeRock AS
setlocal
set OPENDJ_INVOKE_CLASS="com.forgerock.opendj.ldap.tools.AddRate"
set SCRIPT_NAME=addrate
for %%i in (%~sf0) do call "%%~dPsi\..\lib\_client-script.bat" %*
opendj-ldap-toolkit/src/main/assembly/bin/addrate
New file
@@ -0,0 +1,36 @@
#!/bin/sh
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License, Version 1.0 only
# (the "License").  You may not use this file except in compliance
# with the License.
#
# You can obtain a copy of the license at
# trunk/opendj3/legal-notices/CDDLv1_0.txt
# or http://forgerock.org/license/CDDLv1.0.html.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at
# trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
# add the following below this CDDL HEADER, with the fields enclosed
# by brackets "[]" replaced with your own identifying information:
#      Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
#      Copyright 2014 ForgeRock AS
OPENDJ_INVOKE_CLASS="com.forgerock.opendj.ldap.tools.AddRate"
export OPENDJ_INVOKE_CLASS
SCRIPT_NAME="addrate"
export SCRIPT_NAME
SCRIPT_DIR=`dirname "${0}"`
"${SCRIPT_DIR}/../lib/_client-script.sh" "${@}"
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/AddRate.java
New file
@@ -0,0 +1,429 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2014 ForgeRock AS
 */
package com.forgerock.opendj.ldap.tools;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.Utils.filterExitCode;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.DeleteRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldif.EntryGenerator;
import com.forgerock.opendj.cli.ArgumentException;
import com.forgerock.opendj.cli.ArgumentParser;
import com.forgerock.opendj.cli.BooleanArgument;
import com.forgerock.opendj.cli.CommonArguments;
import com.forgerock.opendj.cli.ConnectionFactoryProvider;
import com.forgerock.opendj.cli.ConsoleApplication;
import com.forgerock.opendj.cli.IntegerArgument;
import com.forgerock.opendj.cli.MultiChoiceArgument;
import com.forgerock.opendj.cli.StringArgument;
/**
 * A load generation tool that can be used to load a Directory Server with Add
 * and Delete requests using one or more LDAP connections.
 */
public class AddRate extends ConsoleApplication {
    private static final class AddPerformanceRunner extends PerformanceRunner {
        private final class AddStatsHandler extends UpdateStatsResultHandler<Result> {
            private String entryDN;
            private AddStatsHandler(final long currentTime, String entryDN) {
                super(currentTime);
                this.entryDN = entryDN;
            }
            @Override
            public void handleResult(final Result result) {
                super.handleResult(result);
                switch (delStrategy) {
                case RANDOM:
                    long newKey;
                    do {
                        newKey = randomSeq.get().nextInt();
                    } while (dnEntriesAdded.putIfAbsent(newKey, this.entryDN) != null);
                    break;
                case FIFO:
                    long uniqueTime = currentTime;
                    while (dnEntriesAdded.putIfAbsent(uniqueTime, this.entryDN) != null) {
                        uniqueTime++;
                    }
                    break;
                default:
                    break;
                }
                nbAdd.getAndIncrement();
            }
        }
        private final class DeleteStatsHandler extends UpdateStatsResultHandler<Result> {
            private DeleteStatsHandler(final long startTime) {
                super(startTime);
            }
            @Override
            public void handleResult(final Result result) {
                super.handleResult(result);
                nbDelete.getAndIncrement();
            }
        }
        private final class AddRateStatsThread extends StatsThread {
            private final String[] extraColumn = new String[1];
            private AddRateStatsThread() {
                super(new String[] { "Add%" });
            }
            @Override
            String[] getAdditionalColumns() {
                final int nbAddStat = nbAdd.getAndSet(0);
                final int nbDelStat = nbDelete.getAndSet(0);
                final int total = nbAddStat + nbDelStat;
                extraColumn[0] = String.format("%.2f", total > 0 ? ((double) nbAddStat / total) * 100 : 0.0);
                return extraColumn;
            }
        }
        private final class AddWorkerThread extends WorkerThread {
            AddWorkerThread(Connection connection, ConnectionFactory connectionFactory) {
                super(connection, connectionFactory);
            }
            @Override
            public FutureResult<?> performOperation(Connection connection,
                    DataSource[] dataSources, long currentTime) {
                if (needsDelete(currentTime)) {
                    DeleteRequest dr = Requests.newDeleteRequest(getDNEntryToRemove());
                    return connection.deleteAsync(dr, null, new DeleteStatsHandler(currentTime));
                } else {
                    return performAddOperation(connection, currentTime);
                }
            }
            private FutureResult<?> performAddOperation(Connection connection, long currentTime) {
                try {
                    Entry entry;
                    synchronized (generator) {
                        entry = generator.readEntry();
                    }
                    AddRequest ar = Requests.newAddRequest(entry);
                    return connection
                        .addAsync(ar, null, new AddStatsHandler(currentTime, entry.getName().toString()));
                } catch (IOException e) {
                    // faking an error result by notifying the Handler
                    UpdateStatsResultHandler<Result> resHandler = new UpdateStatsResultHandler<Result>(currentTime);
                    resHandler.handleErrorResult(ErrorResultException.newErrorResult(ResultCode.OTHER, e));
                    return null;
                }
            }
            private boolean needsDelete(final long currentTime) {
                if (dnEntriesAdded.isEmpty() || delStrategy == DeleteStrategy.OFF) {
                    return false;
                }
                switch (delThreshold) {
                case SIZE_THRESHOLD:
                    return dnEntriesAdded.size() > sizeThreshold;
                case AGE_THRESHOLD:
                    long olderEntryTimestamp = (Long) dnEntriesAdded.firstKey();
                    return (olderEntryTimestamp + timeToWait) < currentTime;
                default:
                    return false;
                }
            }
            private String getDNEntryToRemove() {
                String removedEntry = null;
                while (removedEntry == null) {
                    long minKey = dnEntriesAdded.firstKey();
                    long maxKey = dnEntriesAdded.lastKey();
                    long randomIndex = Math.round(Math.random() * (maxKey - minKey) + minKey);
                    Long key = dnEntriesAdded.ceilingKey(randomIndex);
                    if (key != null) {
                        removedEntry = dnEntriesAdded.remove(key);
                    }
                }
                return removedEntry;
            }
        }
        private final ConcurrentSkipListMap<Long, String> dnEntriesAdded =
            new ConcurrentSkipListMap<Long, String>();
        private final ThreadLocal<Random> randomSeq = new ThreadLocal<Random>() {
            @Override
            protected Random initialValue() {
                return new Random();
            }
        };
        private EntryGenerator generator;
        private DeleteStrategy delStrategy;
        private DeleteThreshold delThreshold;
        private Integer sizeThreshold;
        private long timeToWait;
        private final AtomicInteger nbAdd = new AtomicInteger();
        private final AtomicInteger nbDelete = new AtomicInteger();
        private AddPerformanceRunner(final PerformanceRunnerOptions options) throws ArgumentException {
            super(options);
        }
        @Override
        WorkerThread newWorkerThread(Connection connection, ConnectionFactory connectionFactory) {
            return new AddWorkerThread(connection, connectionFactory);
        }
        @Override
        StatsThread newStatsThread() {
            return new AddRateStatsThread();
        }
        public void validate(MultiChoiceArgument<DeleteStrategy> delModeArg, IntegerArgument delSizeThresholdArg,
                                IntegerArgument delAgeThresholdArg) throws ArgumentException {
            super.validate();
            delStrategy = delModeArg.getTypedValue();
            // Check for inconsistent use cases
            if (delSizeThresholdArg.isPresent() && delAgeThresholdArg.isPresent()) {
                throw new ArgumentException(ERR_ADDRATE_THRESHOLD_SIZE_AND_AGE.get());
            } else if (delStrategy == DeleteStrategy.OFF
                && (delSizeThresholdArg.isPresent() || delAgeThresholdArg.isPresent())) {
                throw new ArgumentException(ERR_ADDRATE_DELMODE_OFF_THRESHOLD_ON.get());
            } else if (delStrategy == DeleteStrategy.RANDOM && delAgeThresholdArg.isPresent()) {
                throw new ArgumentException(ERR_ADDRATE_DELMODE_RAND_THRESHOLD_AGE.get());
            }
            if (delStrategy != DeleteStrategy.OFF) {
                delThreshold =
                    delAgeThresholdArg.isPresent() ? DeleteThreshold.AGE_THRESHOLD : DeleteThreshold.SIZE_THRESHOLD;
                if (delThreshold == DeleteThreshold.SIZE_THRESHOLD) {
                    sizeThreshold = delSizeThresholdArg.getIntValue();
                } else {
                    timeToWait = delAgeThresholdArg.getIntValue() * 1000000000L;
                }
            }
        }
    }
    private enum DeleteStrategy {
        OFF, RANDOM, FIFO;
    }
    private enum DeleteThreshold {
        SIZE_THRESHOLD, AGE_THRESHOLD, OFF;
    }
    private static final int EXIT_CODE_SUCCESS = 0;
    /**
     * The main method for AddRate tool.
     *
     * @param args
     *            The command-line arguments provided to this program.
     */
    public static void main(final String[] args) {
        final int retCode = new AddRate().run(args);
        System.exit(filterExitCode(retCode));
    }
    private BooleanArgument verbose;
    private BooleanArgument scriptFriendly;
    private AddRate() {
        // Nothing to do
    }
    AddRate(PrintStream out, PrintStream err) {
        super(out, err);
    }
    /** {@inheritDoc} */
    @Override
    public boolean isInteractive() {
        return false;
    }
    /** {@inheritDoc} */
    @Override
    public boolean isScriptFriendly() {
        return scriptFriendly.isPresent();
    }
    /** {@inheritDoc} */
    @Override
    public boolean isVerbose() {
        return verbose.isPresent();
    }
    int run(final String[] args) {
        // Create the command-line argument parser for use with this program.
        final LocalizableMessage toolDescription = INFO_ADDRATE_TOOL_DESCRIPTION.get();
        final ArgumentParser argParser =
            new ArgumentParser(AddRate.class.getName(), toolDescription, false, true, 1, 1, "template-file-path");
        ConnectionFactoryProvider connectionFactoryProvider;
        ConnectionFactory connectionFactory;
        AddPerformanceRunner runner;
        /* Entries generation parameters */
        IntegerArgument randomSeedArg;
        StringArgument resourcePathArg;
        StringArgument constantsArg;
        /* addrate specifics arguments */
        MultiChoiceArgument<DeleteStrategy> deleteMode;
        IntegerArgument deleteSizeThreshold;
        IntegerArgument deleteAgeThreshold;
        try {
            Utils.setDefaultPerfToolProperties();
            PerformanceRunnerOptions options = new PerformanceRunnerOptions(argParser, this);
            options.setSupportsGeneratorArgument(false);
            connectionFactoryProvider = new ConnectionFactoryProvider(argParser, this);
            runner = new AddPerformanceRunner(options);
            addCommonArguments(argParser);
            /* Entries generation parameters */
            resourcePathArg =
                new StringArgument("resourcepath", 'r', MakeLDIF.OPTION_LONG_RESOURCE_PATH, false, false, true,
                    INFO_PATH_PLACEHOLDER.get(), null, null, INFO_ADDRATE_DESCRIPTION_RESOURCE_PATH.get());
            argParser.addArgument(resourcePathArg);
            randomSeedArg =
                new IntegerArgument("randomseed", 'R', OPTION_LONG_RANDOM_SEED, false, false, true,
                    INFO_SEED_PLACEHOLDER.get(), 0, null, INFO_ADDRATE_DESCRIPTION_SEED.get());
            argParser.addArgument(randomSeedArg);
            constantsArg =
                new StringArgument("constant", 'g', MakeLDIF.OPTION_LONG_CONSTANT, false, true, true,
                    INFO_CONSTANT_PLACEHOLDER.get(), null, null, INFO_ADDRATE_DESCRIPTION_CONSTANT.get());
            argParser.addArgument(constantsArg);
            /* addrate specifics arguments */
            deleteMode =
                new MultiChoiceArgument<DeleteStrategy>("deletemode", 'C', "deleteMode", false, true,
                    INFO_DELETEMODE_PLACEHOLDER.get(), Arrays.asList(DeleteStrategy.values()), false,
                    INFO_ADDRATE_DESCRIPTION_DELETEMODE.get());
            deleteMode.setDefaultValue(DeleteStrategy.FIFO.toString());
            argParser.addArgument(deleteMode);
            deleteSizeThreshold =
                new IntegerArgument("deletesizethreshold", 's', "deleteSizeThreshold", false, true,
                    INFO_DELETESIZETHRESHOLD_PLACEHOLDER.get(), INFO_ADDRATE_DESCRIPTION_DELETESIZETHRESHOLD.get());
            deleteSizeThreshold.setDefaultValue(String.valueOf(10000));
            argParser.addArgument(deleteSizeThreshold);
            deleteAgeThreshold =
                new IntegerArgument("deleteagethreshold", 'a', "deleteAgeThreshold", false, true,
                    INFO_DELETEAGETHRESHOLD_PLACEHOLDER.get(), INFO_ADDRATE_DESCRIPTION_DELETEAGETHRESHOLD.get());
            argParser.addArgument(deleteAgeThreshold);
        } catch (final ArgumentException ae) {
            errPrintln(ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
            return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
        }
        // Parse the command-line arguments provided to this program.
        try {
            argParser.parseArguments(args);
            if (argParser.usageOrVersionDisplayed()) {
                return EXIT_CODE_SUCCESS;
            }
            connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
            runner.validate(deleteMode, deleteSizeThreshold, deleteAgeThreshold);
        } catch (final ArgumentException ae) {
            final LocalizableMessage message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
            errPrintln(message);
            errPrintln(argParser.getUsageMessage());
            return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
        }
        final String templatePath = argParser.getTrailingArguments().get(0);
        runner.generator =
            MakeLDIF.createGenerator(templatePath, resourcePathArg, randomSeedArg, constantsArg, false, this);
        return runner.run(connectionFactory);
    }
    private void addCommonArguments(ArgumentParser argParser) throws ArgumentException {
        StringArgument propertiesFileArgument = CommonArguments.getPropertiesFile();
        argParser.addArgument(propertiesFileArgument);
        argParser.setFilePropertiesArgument(propertiesFileArgument);
        BooleanArgument noPropertiesFileArgument = CommonArguments.getNoPropertiesFile();
        argParser.addArgument(noPropertiesFileArgument);
        argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
        BooleanArgument showUsage = CommonArguments.getShowUsage();
        argParser.addArgument(showUsage);
        argParser.setUsageArgument(showUsage, getOutputStream());
        verbose = CommonArguments.getVerbose();
        argParser.addArgument(verbose);
        scriptFriendly =
            new BooleanArgument("scriptFriendly", 'S', "scriptFriendly", INFO_DESCRIPTION_SCRIPT_FRIENDLY.get());
        scriptFriendly.setPropertyName("scriptFriendly");
        argParser.addArgument(scriptFriendly);
    }
}
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/AuthRate.java
@@ -321,9 +321,9 @@
        private BindRequest bindRequest;
        private int invalidCredPercent;
        private BindPerformanceRunner(final ArgumentParser argParser, final ConsoleApplication app)
        private BindPerformanceRunner(final PerformanceRunnerOptions options)
                throws ArgumentException {
            super(argParser, app, true, true, true);
            super(options);
        }
        @Override
@@ -408,9 +408,13 @@
        try {
            setDefaultPerfToolProperties();
            PerformanceRunnerOptions options = new PerformanceRunnerOptions(argParser, this);
            options.setSupportsRebind(false);
            options.setSupportsAsynchronousRequests(false);
            options.setSupportsMultipleThreadsPerConnection(false);
            connectionFactoryProvider = new ConnectionFactoryProvider(argParser, this);
            runner = new BindPerformanceRunner(argParser, this);
            runner = new BindPerformanceRunner(options);
            propertiesFileArgument = CommonArguments.getPropertiesFile();
            argParser.addArgument(propertiesFileArgument);
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/MakeLDIF.java
@@ -55,6 +55,16 @@
 */
public final class MakeLDIF extends ConsoleApplication {
    /**
     * The value for the constant option in LDIF generator tools.
     */
    public static final String OPTION_LONG_CONSTANT = "constant";
    /**
     * The value for the path to look for LDIF resources (e.g data files).
     */
    public static final String OPTION_LONG_RESOURCE_PATH = "resourcePath";
    private static final int EXIT_CODE_SUCCESS = 0;
    private static final int EXIT_CODE_FAILURE = 1;
@@ -84,7 +94,7 @@
        StringArgument resourcePath;
        StringArgument constants;
        try {
            resourcePath = new StringArgument("resourcepath", 'r', "resourcePath", false, false, true,
            resourcePath = new StringArgument("resourcepath", 'r', OPTION_LONG_RESOURCE_PATH, false, false, true,
                    INFO_PATH_PLACEHOLDER.get(), null, null, INFO_MAKELDIF_DESCRIPTION_RESOURCE_PATH.get());
            argParser.addArgument(resourcePath);
@@ -97,7 +107,7 @@
                    false, true, INFO_SEED_PLACEHOLDER.get(), 0, null, INFO_MAKELDIF_DESCRIPTION_SEED.get());
            argParser.addArgument(randomSeed);
            constants = new StringArgument("constant", 'c', "constant", false, true, true,
            constants = new StringArgument("constant", 'c', OPTION_LONG_CONSTANT, false, true, true,
                    INFO_CONSTANT_PLACEHOLDER.get(),
                    null, null, INFO_MAKELDIF_DESCRIPTION_CONSTANT.get());
            argParser.addArgument(constants);
@@ -168,17 +178,15 @@
        return EXIT_CODE_SUCCESS;
    }
    /**
     * Returns the initialized generator, or null if a problem occurs.
     */
    private EntryGenerator createGenerator(final String templatePath, final StringArgument resourcePath,
            final IntegerArgument randomSeedArg, final StringArgument constants) {
        final EntryGenerator generator = new EntryGenerator(templatePath);
    static EntryGenerator createGenerator(final String templatePath, final StringArgument resourcePath,
                                            final IntegerArgument randomSeedArg, final StringArgument constants,
                                            final boolean generateBranches, final ConsoleApplication app) {
        final EntryGenerator generator = new EntryGenerator(templatePath).setGenerateBranches(generateBranches);
        if (resourcePath.isPresent()) {
            final File resourceDir = new File(resourcePath.getValue());
            if (!resourceDir.exists()) {
                errPrintln(ERR_MAKELDIF_NO_SUCH_RESOURCE_DIRECTORY.get(resourcePath.getValue()));
                app.errPrintln(ERR_LDIF_GEN_TOOL_NO_SUCH_RESOURCE_DIRECTORY.get(resourcePath.getValue()));
                generator.close();
                return null;
            }
@@ -189,14 +197,14 @@
            try {
                generator.setRandomSeed(randomSeedArg.getIntValue());
            } catch (ArgumentException ae) {
                errPrintln(ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
                app.errPrintln(ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
                generator.close();
                return null;
            }
        }
        if (constants.isPresent()) {
            if (!addConstantsToGenerator(constants, generator)) {
            if (!addConstantsToGenerator(constants, generator, app)) {
                generator.close();
                return null;
            }
@@ -206,7 +214,7 @@
        try {
            generator.hasNext();
        } catch (IOException e) {
            errPrintln(ERR_MAKELDIF_EXCEPTION_DURING_PARSE.get(e.getMessage()));
            app.errPrintln(ERR_LDIF_GEN_TOOL_EXCEPTION_DURING_PARSE.get(e.getMessage()));
            generator.close();
            return null;
        }
@@ -217,11 +225,12 @@
    /**
     * Returns true if all constants are added to generator, false otherwise.
     */
    private boolean addConstantsToGenerator(StringArgument constants, EntryGenerator generator) {
    private static boolean addConstantsToGenerator(StringArgument constants, EntryGenerator generator,
                                                       final ConsoleApplication app) {
        for (final String constant : constants.getValues()) {
            final String[] chunks = constant.split("=");
            if (chunks.length != 2) {
                errPrintln(ERR_CONSTANT_ARG_CANNOT_DECODE.get(constant));
                app.errPrintln(ERR_CONSTANT_ARG_CANNOT_DECODE.get(constant));
                return false;
            }
            generator.setConstant(chunks[0], chunks[1]);
@@ -229,8 +238,14 @@
        return true;
    }
    private EntryGenerator createGenerator(final String templatePath, final StringArgument resourcePath,
            final IntegerArgument randomSeedArg, final StringArgument constants) {
        return createGenerator(templatePath, resourcePath, randomSeedArg, constants, true, this);
    }
    /**
     * Returns true if generation is successfull, false otherwise.
     * Returns true if generation is successful, false otherwise.
     */
    private boolean generateEntries(final EntryGenerator generator, final LDIFEntryWriter writer,
            final StringArgument ldifFile) {
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/ModRate.java
@@ -102,9 +102,9 @@
        private String baseDN;
        private String[] modStrings;
        private ModifyPerformanceRunner(final ArgumentParser argParser, final ConsoleApplication app)
        private ModifyPerformanceRunner(final PerformanceRunnerOptions options)
                throws ArgumentException {
            super(argParser, app, false, false, false);
            super(options);
        }
        @Override
@@ -175,7 +175,7 @@
            Utils.setDefaultPerfToolProperties();
            connectionFactoryProvider = new ConnectionFactoryProvider(argParser, this);
            runner = new ModifyPerformanceRunner(argParser, this);
            runner = new ModifyPerformanceRunner(new PerformanceRunnerOptions(argParser, this));
            propertiesFileArgument = CommonArguments.getPropertiesFile();
            argParser.addArgument(propertiesFileArgument);
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java
@@ -28,6 +28,7 @@
package com.forgerock.opendj.ldap.tools;
import static org.forgerock.util.Utils.closeSilently;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import java.io.IOException;
import java.lang.management.GarbageCollectorMXBean;
@@ -70,39 +71,22 @@
     */
    class StatsThread extends Thread {
        private final String[] additionalColumns;
        private final List<GarbageCollectorMXBean> beans;
        private final Set<Double> percentiles;
        private final int numColumns;
        private ReversableArray etimes = new ReversableArray(100000);
        private final ReversableArray array = new ReversableArray(200000);
        protected long totalSuccessCount;
        protected long totalOperationCount;
        protected long totalFailedCount;
        protected long totalWaitTime;
        protected int successCount;
        protected int operationCount;
        protected int failedCount;
        protected long waitTime;
        protected long lastStatTime;
        protected long lastGCDuration;
        protected double recentDuration;
        protected double averageDuration;
        public StatsThread(final String[] additionalColumns) {
@@ -351,10 +335,10 @@
     *            The type of expected result.
     */
    class UpdateStatsResultHandler<S extends Result> implements ResultHandler<S> {
        private final long startTime;
        protected final long currentTime;
        UpdateStatsResultHandler(final long startTime) {
            this.startTime = startTime;
        UpdateStatsResultHandler(final long currentTime) {
            this.currentTime = currentTime;
        }
        @Override
@@ -371,7 +355,7 @@
        }
        private void updateStats() {
            final long eTime = System.nanoTime() - startTime;
            final long eTime = System.nanoTime() - currentTime;
            waitRecentTime.getAndAdd(eTime);
            synchronized (this) {
                final ReversableArray array = eTimeBuffer.get();
@@ -451,7 +435,9 @@
                count++;
                if (!isAsync) {
                    try {
                        future.get();
                        if (future != null) {
                            future.get();
                        }
                    } catch (final InterruptedException e) {
                        // Ignore and check stop requested
                        continue;
@@ -603,20 +589,15 @@
    }
    private static final String[] EMPTY_STRINGS = new String[0];
    private final AtomicInteger operationRecentCount = new AtomicInteger();
    protected final AtomicInteger successRecentCount = new AtomicInteger();
    protected final AtomicInteger failedRecentCount = new AtomicInteger();
    private final AtomicLong waitRecentTime = new AtomicLong();
    private final AtomicReference<ReversableArray> eTimeBuffer =
            new AtomicReference<ReversableArray>(new ReversableArray(100000));
    private final ConsoleApplication app;
    private DataSource[] dataSourcePrototypes;
    // Thread local copies of the data sources
@@ -638,51 +619,34 @@
    };
    private volatile boolean stopRequested;
    private int numThreads;
    private int numConnections;
    private int targetThroughput;
    private int maxIterations;
    private boolean isAsync;
    private boolean noRebind;
    private int statsInterval;
    private final IntegerArgument numThreadsArgument;
    private final IntegerArgument maxIterationsArgument;
    private final IntegerArgument statsIntervalArgument;
    private final IntegerArgument targetThroughputArgument;
    private final IntegerArgument numConnectionsArgument;
    private final IntegerArgument percentilesArgument;
    private final BooleanArgument keepConnectionsOpen;
    private final BooleanArgument noRebindArgument;
    private final BooleanArgument asyncArgument;
    private final StringArgument arguments;
    PerformanceRunner(final ArgumentParser argParser, final ConsoleApplication app,
            final boolean neverRebind, final boolean neverAsynchronous,
            final boolean alwaysSingleThreaded) throws ArgumentException {
        this.app = app;
    PerformanceRunner(final PerformanceRunnerOptions options) throws ArgumentException {
        ArgumentParser argParser = options.getArgumentParser();
        this.app = options.getConsoleApplication();
        numThreadsArgument =
                new IntegerArgument("numThreads", 't', "numThreads", false, false, true,
                        LocalizableMessage.raw("{numThreads}"), 1, null, true, 1, false, 0,
                        LocalizableMessage.raw("Number of worker threads per connection"));
        numThreadsArgument.setPropertyName("numThreads");
        if (!alwaysSingleThreaded) {
        if (options.supportsMultipleThreadsPerConnection()) {
            argParser.addArgument(numThreadsArgument);
        } else {
            numThreadsArgument.addValue("1");
@@ -735,7 +699,7 @@
                new BooleanArgument("noRebind", 'F', "noRebind", LocalizableMessage
                        .raw("Keep connections open and don't rebind"));
        noRebindArgument.setPropertyName("noRebind");
        if (!neverRebind) {
        if (options.supportsRebind()) {
            argParser.addArgument(noRebindArgument);
        }
@@ -744,7 +708,7 @@
                        .raw("Use asynchronous mode and don't "
                                + "wait for results before sending the next request"));
        asyncArgument.setPropertyName("asynchronous");
        if (!neverAsynchronous) {
        if (options.supportsAsynchronousRequests()) {
            argParser.addArgument(asyncArgument);
        }
@@ -767,7 +731,9 @@
                                        + "arguments, they can be generated per iteration with the "
                                        + "following functions: " + StaticUtils.EOL
                                        + DataSource.getUsage()));
        argParser.addArgument(arguments);
        if (options.supportsGeneratorArgument()) {
            argParser.addArgument(arguments);
        }
    }
    @Override
@@ -804,15 +770,19 @@
        noRebind = noRebindArgument.isPresent();
        if (!noRebindArgument.isPresent() && this.numThreads > 1) {
            throw new ArgumentException(LocalizableMessage.raw("--"
                    + noRebindArgument.getLongIdentifier() + " must be used if --"
                    + numThreadsArgument.getLongIdentifier() + " is > 1"));
            throw new ArgumentException(ERR_TOOL_ARG_MUST_BE_USED_WHEN_ARG_CONDITION.get(
                "--" + noRebindArgument.getLongIdentifier(), "--" + numThreadsArgument.getLongIdentifier(), "> 1"));
        }
        if (!noRebindArgument.isPresent() && asyncArgument.isPresent()) {
            throw new ArgumentException(LocalizableMessage.raw("--"
                    + noRebindArgument.getLongIdentifier() + " must be used when using --"
                    + asyncArgument.getLongIdentifier()));
            throw new ArgumentException(ERR_TOOL_ARG_NEEDED_WHEN_USING_ARG.get(
                "--" + noRebindArgument.getLongIdentifier(), asyncArgument.getLongIdentifier()));
        }
        if (maxIterationsArgument.isPresent() && maxIterations <= 0) {
            throw new ArgumentException(ERR_TOOL_NOT_ENOUGH_ITERATIONS.get(
                "--" + maxIterationsArgument.getLongIdentifier(), numConnections * numThreads,
                numConnectionsArgument.getLongIdentifier(), numThreadsArgument.getLongIdentifier()));
        }
        dataSourcePrototypes = DataSource.parse(arguments.getValues());
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunnerOptions.java
New file
@@ -0,0 +1,90 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2014 ForgeRock AS
 */
package com.forgerock.opendj.ldap.tools;
import com.forgerock.opendj.cli.ArgumentParser;
import com.forgerock.opendj.cli.ConsoleApplication;
/**
 * SDK Performance Runner options wrapper.
 */
class PerformanceRunnerOptions {
    private ArgumentParser argParser;
    private ConsoleApplication app;
    private boolean supportsRebind = true;
    private boolean supportAsynchronousRequests = true;
    private boolean supportsMultipleThreadsPerConnection = true;
    private boolean supportsGeneratorArgument = true;
    PerformanceRunnerOptions(ArgumentParser argParser, ConsoleApplication app) {
        super();
        this.argParser = argParser;
        this.app = app;
    }
    boolean supportsRebind() {
        return supportsRebind;
    }
    void setSupportsRebind(boolean supportsRebind) {
        this.supportsRebind = !supportsRebind;
    }
    boolean supportsAsynchronousRequests() {
        return supportAsynchronousRequests;
    }
    void setSupportsAsynchronousRequests(boolean supportAsynchronousRequests) {
        this.supportAsynchronousRequests = supportAsynchronousRequests;
    }
    boolean supportsMultipleThreadsPerConnection() {
        return supportsMultipleThreadsPerConnection;
    }
    void setSupportsMultipleThreadsPerConnection(boolean supportsMultipleThreadsPerConnection) {
        this.supportsMultipleThreadsPerConnection = supportsMultipleThreadsPerConnection;
    }
    boolean supportsGeneratorArgument() {
        return supportsGeneratorArgument;
    }
    void setSupportsGeneratorArgument(boolean supportsGeneratorArgument) {
        this.supportsGeneratorArgument = supportsGeneratorArgument;
    }
    ArgumentParser getArgumentParser() {
        return argParser;
    }
    ConsoleApplication getConsoleApplication() {
        return app;
    }
}
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/SearchRate.java
@@ -139,9 +139,9 @@
        private DereferenceAliasesPolicy dereferencesAliasesPolicy;
        private String[] attributes;
        private SearchPerformanceRunner(final ArgumentParser argParser, final ConsoleApplication app)
        private SearchPerformanceRunner(final PerformanceRunnerOptions options)
                throws ArgumentException {
            super(argParser, app, false, false, false);
            super(options);
        }
        @Override
@@ -225,7 +225,7 @@
            Utils.setDefaultPerfToolProperties();
            connectionFactoryProvider = new ConnectionFactoryProvider(argParser, this);
            runner = new SearchPerformanceRunner(argParser, this);
            runner = new SearchPerformanceRunner(new PerformanceRunnerOptions(argParser, this));
            propertiesFileArgument = CommonArguments.getPropertiesFile();
            argParser.addArgument(propertiesFileArgument);
opendj-ldap-toolkit/src/main/resources/com/forgerock/opendj/ldap/tools/tools.properties
@@ -22,7 +22,7 @@
#
#
#      Copyright 2010 Sun Microsystems, Inc.
#      Portions copyright 2012 ForgeRock AS.
#      Portions copyright 2012-2014 ForgeRock AS.
#
#
# Utility messages
@@ -429,6 +429,15 @@
  user-defined searches.\n\n\
  Example:\n\n\ \ searchrate -p 1389 -D "cn=directory manager" -w password \\\n\
  \ \ \ \ -F -c 4 -t 4 -b "dc=example,dc=com" -g "rand(0,2000)" "(uid=user.%%d)"
INFO_ADDRATE_TOOL_DESCRIPTION=This utility can be used to measure \
  add and optionally delete throughput and response time of a directory server using \
  user-defined entries.\n\
  \nExamples:\n \  This example is adding entries and randomly deleting them while \
  the number of entries added is greater than 10,000: \n \
  addrate -p 1389 -f -c 10 -d rand -s 10000 addrate.template \n \
  This example adds entries and starts to delete them in the same \
  order if their age is greater than a certain time: \n \
  addrate -p 1389 -f -c 10 -d fifo -a 2 addrate.template
INFO_SEARCHRATE_TOOL_DESCRIPTION_BASEDN=Base DN format string.
INFO_MODRATE_TOOL_DESCRIPTION=This utility can be used to measure \
  modify throughput and response time of a directory service using \
@@ -471,6 +480,14 @@
 files and report the differences in LDIF format
INFO_LDIFSEARCH_TOOL_DESCRIPTION=This utility can be used to perform search \
 operations against entries contained in an LDIF file
ERR_LDIF_GEN_TOOL_EXCEPTION_DURING_PARSE=An error occurred while \
 parsing template file:  %s
ERR_LDIF_GEN_TOOL_NO_SUCH_RESOURCE_DIRECTORY=The specified resource \
 directory %s does not exist
ERR_TOOL_NOT_ENOUGH_ITERATIONS=%s argument must be greater than or equal to %s \
 (%s per %s)
ERR_TOOL_ARG_NEEDED_WHEN_USING_ARG=%s must be used when using %s
ERR_TOOL_ARG_MUST_BE_USED_WHEN_ARG_CONDITION=%s must be used if %s is %s
 #
 # MakeLDIF tool
 #
@@ -487,18 +504,34 @@
INFO_MAKELDIF_DESCRIPTION_HELP=Show this usage information
INFO_MAKELDIF_DESCRIPTION_RESOURCE_PATH=Path to look for \
 MakeLDIF resources (e.g., data files)
ERR_MAKELDIF_NO_SUCH_RESOURCE_DIRECTORY=The specified resource \
 directory %s could not be found
INFO_MAKELDIF_PROCESSED_N_ENTRIES=Processed %d entries
INFO_MAKELDIF_PROCESSING_COMPLETE=LDIF processing complete. %d entries \
 written
ERR_MAKELDIF_EXCEPTION_DURING_PARSE=An error occurred while \
 parsing template file :  %s
ERR_MAKELDIF_UNABLE_TO_CREATE_LDIF=An error occurred while \
 attempting to open LDIF file %s for writing:  %s
ERR_MAKELDIF_ERROR_WRITING_LDIF=An error occurred while writing data \
 to LDIF file %s:  %s
ERR_MAKELDIF_EXCEPTION_DURING_PROCESSING=An error occurred while \
 processing :  %s
ERR_CONSTANT_ARG_CANNOT_DECODE=Unable to parse a constant argument, \
ERR_CONSTANT_ARG_CANNOT_DECODE=Unable to parse a constant argument \
 expecting name=value but got %s
#
# AddRate Tool
#
INFO_ADDRATE_DESCRIPTION_RESOURCE_PATH=Path to look for template resources (e.g. data files)
INFO_ADDRATE_DESCRIPTION_SEED=The seed to use for initializing the random number generator
INFO_ADDRATE_DESCRIPTION_CONSTANT=A constant that overrides the value set in the template file
INFO_ADDRATE_DESCRIPTION_DELETEMODE=The algorithm used for selecting entries to be deleted which \
 must be one of "fifo", "random", or "off".
INFO_ADDRATE_DESCRIPTION_DELETESIZETHRESHOLD=Specifies the number of entries \
 to be added before deletion begins
INFO_ADDRATE_DESCRIPTION_DELETEAGETHRESHOLD=Specifies the age at which added entries \
 will become candidates for deletion
INFO_DELETEMODE_PLACEHOLDER={fifo | random | off}
INFO_DELETESIZETHRESHOLD_PLACEHOLDER={count}
INFO_DELETEAGETHRESHOLD_PLACEHOLDER={seconds}
ERR_ADDRATE_DELMODE_OFF_THRESHOLD_ON=A deletion threshold should not be specified when deletion is disabled
ERR_ADDRATE_THRESHOLD_SIZE_AND_AGE=Size and age based deletion thresholds were both \
 specified, but only only one at a time is supported
ERR_ADDRATE_DELMODE_RAND_THRESHOLD_AGE=A age based deletion threshold should not be used \
 with a random deletion mode
opendj-ldap-toolkit/src/test/java/com/forgerock/opendj/ldap/tools/AddRateITCase.java
New file
@@ -0,0 +1,115 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2014 ForgeRock AS.
 */
package com.forgerock.opendj.ldap.tools;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.ERR_ADDRATE_DELMODE_OFF_THRESHOLD_ON;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.ERR_ADDRATE_DELMODE_RAND_THRESHOLD_AGE;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.ERR_ADDRATE_THRESHOLD_SIZE_AND_AGE;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.INFO_ADDRATE_TOOL_DESCRIPTION;
import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.util.Utils.closeSilently;
import java.io.PrintStream;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.forgerock.opendj.ldap.TestCaseUtils;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class AddRateITCase extends ToolsITCase {
    private static final String TEMPLATE_NAME = "addrate.template";
    private static final String ADD_PERCENT_TEXT = "Add%";
    private static final int THROUGHPUT_COLUMN = 1;
    private static final int ERR_PER_SEC_COLUMN_NUMBER = 8;
    @DataProvider
    public Object[][] addRateArgs() throws Exception {
        String[] commonArgs =
            args("-h", TestCaseUtils.getServerSocketAddress().getHostName(),
                "-p", Integer.toString(TestCaseUtils.getServerSocketAddress().getPort()),
                "-c", "1", "-t", "1", "-i", "1", "-m", "1000", "-S");
        return new Object[][] {
            // Check if the help message is correctly prompted
            { args("-H"), INFO_ADDRATE_TOOL_DESCRIPTION.get(), "" },
            // Should report inconsistent use of options
            { args(commonArgs, "-C", "off", "-a", "3", TEMPLATE_NAME), "",
                ERR_ADDRATE_DELMODE_OFF_THRESHOLD_ON.get() },
            { args(commonArgs, "-C", "off", "-s", "30000", TEMPLATE_NAME), "",
                ERR_ADDRATE_DELMODE_OFF_THRESHOLD_ON.get() },
            { args(commonArgs, "-C", "fifo", "-a", "3", "-s", "20000", TEMPLATE_NAME), "",
                ERR_ADDRATE_THRESHOLD_SIZE_AND_AGE.get() },
            { args(commonArgs, "-C", "random", "-a", "3", TEMPLATE_NAME), "",
                ERR_ADDRATE_DELMODE_RAND_THRESHOLD_AGE.get() },
            // Correct test case
            { args(commonArgs, "-s", "10", TEMPLATE_NAME), ADD_PERCENT_TEXT, "" },
        };
    }
    public String[] args(String[] startLine, String... args) {
        String[] res = new String[startLine.length + args.length];
        System.arraycopy(startLine, 0, res, 0, startLine.length);
        System.arraycopy(args, 0, res, startLine.length, args.length);
        return res;
    }
    @Test(dataProvider = "addRateArgs")
    public void testITAddRate(String[] arguments, Object expectedOut, Object expectedErr) throws Exception {
        ByteStringBuilder out = new ByteStringBuilder();
        ByteStringBuilder err = new ByteStringBuilder();
        PrintStream outStream = new PrintStream(out.asOutputStream());
        PrintStream errStream = new PrintStream(err.asOutputStream());
        try {
            AddRate addRate = new AddRate(outStream, errStream);
            addRate.run(arguments);
            checkOuputStreams(out, err, expectedOut, expectedErr);
            String outContent = out.toString();
            if (outContent.contains(ADD_PERCENT_TEXT)) {
                // Check that there was no error in search
                String[] addRateResLines = outContent.split(System.getProperty("line.separator"));
                // Skip header line
                for (int i = 1; i < addRateResLines.length; i++) {
                    String[] addhRateLineData = addRateResLines[i].split(",");
                    assertThat(addhRateLineData[ERR_PER_SEC_COLUMN_NUMBER].trim()).isEqualTo("0.0");
                    assertThat(addhRateLineData[THROUGHPUT_COLUMN].trim()).isNotEqualTo("0.0");
                }
            }
        } finally {
            closeSilently(outStream, errStream);
        }
    }
}
opendj-ldap-toolkit/src/test/java/com/forgerock/opendj/ldap/tools/MakeLDIFTestCase.java
@@ -68,7 +68,7 @@
              expectedErrOutput(ERR_ERROR_PARSING_ARGS.get("")) },
            { args("-r", "unknown/path" , "example.template"),
              expectedErrOutput(ERR_MAKELDIF_NO_SUCH_RESOURCE_DIRECTORY.get("unknown/path")) },
              expectedErrOutput(ERR_LDIF_GEN_TOOL_NO_SUCH_RESOURCE_DIRECTORY.get("unknown/path")) },
            { args("-o", "unknown/path" , "example.template"),
              expectedErrOutput(ERR_MAKELDIF_UNABLE_TO_CREATE_LDIF.get("unknown/path", "")) },
opendj-ldap-toolkit/src/test/java/com/forgerock/opendj/ldap/tools/ToolsITCase.java
@@ -74,16 +74,19 @@
    }
    private void checkOutputStream(ByteStringBuilder out, Object expectedOutput) {
        String lineSeparator = System.getProperty("line.separator");
        String toCompare = expectedOutput.toString();
        if (expectedOutput instanceof LocalizableMessage) {
            toCompare = wrapText((LocalizableMessage) expectedOutput, MAX_LINE_WIDTH);
        }
        toCompare = toCompare.replace(lineSeparator, " ");
        if (toCompare.isEmpty()) {
            assertThat(out.toString().length()).isEqualTo(0);
        } else {
            assertThat(out.toString()).contains(toCompare);
            assertThat(out.toString().replace(lineSeparator, " ")).contains(toCompare);
        }
    }