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
| | |
| | | */ |
| | | 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>(); |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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 |
| | |
| | | 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); |
| | |
| | | /** 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. |
| | |
| | | /** 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. |
| | |
| | | * 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); |
| | | } |
| | | |
| | | /** |
| | |
| | | * {@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; |
| | |
| | | // 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()) { |
| | |
| | | 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()); |
| | | if (colonPos <= SUBORDINATE_TEMPLATE_LABEL.length()) { |
| | | throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_SUBORDINATE_TEMPLATE_NO_COLON.get( |
| | | lineNumber, element.getLabel(), elementName)); |
| | | } |
| | | final String templateName; |
| | | int numEntries = INFINITE_ENTRIES; |
| | | |
| | | final String templateName = line.substring(SUBORDINATE_TEMPLATE_LABEL.length(), colonPos).trim(); |
| | | if (colonPos <= SUBORDINATE_TEMPLATE_LABEL.length()) { |
| | | //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(); |
| | | |
| | | 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) { |
| | | 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)); |
| | | } |
| | | 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; |
| | | private static final int PARSING_REPLACEMENT_TAG = 1; |
| | | private static final int PARSING_ATTRIBUTE_TAG = 2; |
| | |
| | | * 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())); |
| | |
| | | } |
| | | } |
| | | |
| | | nextEntry = buildBranchEntry(); |
| | | nextEntry = buildBranchEntry(generateBranches); |
| | | } |
| | | |
| | | DN getBranchDN() { |
| | |
| | | |
| | | /** |
| | | * 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); |
| | |
| | | for (int i = 0; i < subordinateTemplates.size(); i++) { |
| | | subordinateTemplates.get(i).reset(entry.getDN(), numEntriesPerTemplate.get(i)); |
| | | } |
| | | |
| | | if (!generateBranches) { |
| | | return null; |
| | | } |
| | | |
| | | return entry; |
| | | } |
| | | |
| | |
| | | /** 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. */ |
| | |
| | | if (nextEntry != null) { |
| | | return true; |
| | | } |
| | | while (entriesCount < numberOfEntries) { |
| | | while ((entriesCount < numberOfEntries) || generateForever()) { |
| | | // get the template entry |
| | | if (!currentEntryIsInitialized) { |
| | | nextEntry = buildTemplateEntry(); |
| | |
| | | return false; |
| | | } |
| | | |
| | | private boolean generateForever() { |
| | | return numberOfEntries < 0; |
| | | } |
| | | |
| | | /** |
| | | * Returns the next generated entry. |
| | | * |
| New file |
| | |
| | | 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}. |
| | |
| | | // 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, |
| New file |
| | |
| | | |
| | | @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" %* |
| | | |
| New file |
| | |
| | | #!/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" "${@}" |
| New file |
| | |
| | | /* |
| | | * 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); |
| | | |
| | | } |
| | | } |
| | |
| | | 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 |
| | |
| | | |
| | | 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); |
| | |
| | | */ |
| | | 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; |
| | | |
| | |
| | | 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); |
| | | |
| | |
| | | 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); |
| | |
| | | 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; |
| | | } |
| | |
| | | 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; |
| | | } |
| | |
| | | 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; |
| | | } |
| | |
| | | /** |
| | | * 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]); |
| | |
| | | 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) { |
| | |
| | | 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 |
| | |
| | | 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); |
| | |
| | | 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; |
| | |
| | | */ |
| | | 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) { |
| | |
| | | * 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 |
| | |
| | | } |
| | | |
| | | 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(); |
| | |
| | | count++; |
| | | if (!isAsync) { |
| | | try { |
| | | if (future != null) { |
| | | future.get(); |
| | | } |
| | | } catch (final InterruptedException e) { |
| | | // Ignore and check stop requested |
| | | continue; |
| | |
| | | } |
| | | |
| | | 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 |
| | |
| | | }; |
| | | |
| | | 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"); |
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | .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); |
| | | } |
| | | |
| | |
| | | + "arguments, they can be generated per iteration with the " |
| | | + "following functions: " + StaticUtils.EOL |
| | | + DataSource.getUsage())); |
| | | if (options.supportsGeneratorArgument()) { |
| | | argParser.addArgument(arguments); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void handleConnectionClosed() { |
| | |
| | | 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()); |
| New file |
| | |
| | | /* |
| | | * 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; |
| | | } |
| | | |
| | | } |
| | |
| | | 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 |
| | |
| | | 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); |
| | |
| | | # |
| | | # |
| | | # Copyright 2010 Sun Microsystems, Inc. |
| | | # Portions copyright 2012 ForgeRock AS. |
| | | # Portions copyright 2012-2014 ForgeRock AS. |
| | | # |
| | | # |
| | | # Utility messages |
| | |
| | | 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 \ |
| | |
| | | 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 |
| | | # |
| | |
| | | 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 |
| New file |
| | |
| | | /* |
| | | * 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); |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | 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", "")) }, |
| | |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | |
| | | } |