/* * Copyright 2008-2010 UnboundID Corp. * All Rights Reserved. */ /* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2008-2010. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.jobs; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import com.slamd.common.SLAMDException; import com.slamd.job.UnableToRunException; import com.slamd.parameter.IntegerParameter; import com.slamd.parameter.InvalidValueException; import com.slamd.parameter.MultiChoiceParameter; import com.slamd.parameter.MultiLineTextParameter; import com.slamd.parameter.Parameter; import com.slamd.parameter.ParameterList; import com.slamd.parameter.PlaceholderParameter; import com.slamd.parameter.StringParameter; import com.slamd.stat.CategoricalTracker; import com.slamd.stat.IncrementalTracker; import com.slamd.stat.RealTimeStatReporter; import com.slamd.stat.StatTracker; import com.slamd.stat.TimeTracker; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPResult; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.util.FixedRateBarrier; /** * This class provides a SLAMD job class that may be used to perform add and * delete operations against an LDAP directory server. */ public class LDAPAddAndDeleteRateJobClass extends LDAPJobClass { /** * The display name for the stat tracker used to track add result codes. */ private static final String STAT_ADD_RESULT_CODES = "Add Result Codes"; /** * The display name for the stat tracker used to track add durations. */ private static final String STAT_ADD_DURATION = "Add Duration (ms)"; /** * The display name for the stat tracker used to track adds completed. */ private static final String STAT_ADDS_COMPLETED = "Adds Completed"; /** * The display name for the stat tracker used to track adds exceeding the * response time threshold. */ private static final String STAT_ADDS_EXCEEDING_THRESHOLD = "Adds Exceeding Response Time Threshold"; /** * The display name for the stat tracker used to track delete result codes. */ private static final String STAT_DELETE_RESULT_CODES = "Delete Result Codes"; /** * The display name for the stat tracker used to track delete durations. */ private static final String STAT_DELETE_DURATION = "Delete Duration (ms)"; /** * The display name for the stat tracker used to track deletes completed. */ private static final String STAT_DELETES_COMPLETED = "Deletes Completed"; /** * The display name for the stat tracker used to track deletes exceeding the * response time threshold. */ private static final String STAT_DELETES_EXCEEDING_THRESHOLD = "Deletes Exceeding Response Time Threshold"; /** * The default template lines to use when generating entries. */ private static final String[] DEFAULT_TEMPLATE_LINES = { "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: <entryNumber>", "givenName: <random:alpha:8>", "sn: <random:alpha:10>", "cn: {givenName} {sn}", "initials: {givenName:1}<random:alpha:1>{sn:1}", "employeeNumber: {uid}", "mail: {uid}@example.com", "userPassword: password", "telephoneNumber: <random:telephone>", "homePhone: <random:telephone>", "pager: <random:telephone>", "mobile: <random:telephone>", "street: <random:numeric:5> <random:alpha:10> Street", "l: <random:alpha:10>", "st: <random:alpha:2>", "postalCode: <random:numeric:5>", "postalAddress: {cn}${street}${l}, {st} {postalCode}", "description: This is the description for {cn}", }; /** * The processing type option indicating that all adds should be performed, * followed by all deletes. */ private static final String PROCESSING_TYPE_ADDS_THEN_DELETES = "Perform all adds, then perform all deletes"; /** * The processing type option indicating that adds and deletes should be * alternated. */ private static final String PROCESSING_TYPE_ALTERNATE_ADDS_AND_DELETES = "Delete each entry immediately after adding it"; /** * The processing type option indicating that only adds should be performed. */ private static final String PROCESSING_TYPE_ONLY_ADDS = "Perform add operations but not delete operations"; /** * The processing type option indicating that only deletes should be * performed. */ private static final String PROCESSING_TYPE_ONLY_DELETES = "Perform delete operations but not add operations"; /** * The set of available processing types. */ private static final String[] PROCESSING_TYPES = { PROCESSING_TYPE_ADDS_THEN_DELETES, PROCESSING_TYPE_ALTERNATE_ADDS_AND_DELETES, PROCESSING_TYPE_ONLY_ADDS, PROCESSING_TYPE_ONLY_DELETES }; // Variables used to hold the values of the parameters. private static boolean alternateAddsAndDeletes; private static boolean performAdds; private static boolean performDeletes; private static int firstEntryNumber; private static int lastEntryNumber; private static int responseTimeThreshold; private static long timeBetweenAddsAndDeletes; private static long timeBetweenRequests; private static String baseDN; private static String rdnAttribute; // Stat trackers used by this job. private CategoricalTracker addResultCodes; private CategoricalTracker deleteResultCodes; private IncrementalTracker addsCompleted; private IncrementalTracker addsExceedingThreshold; private IncrementalTracker deletesCompleted; private IncrementalTracker deletesExceedingThreshold; private TimeTracker addTimer; private TimeTracker deleteTimer; // Random number generators used by this job. private static Random parentRandom; private Random random; // The number of threads that are currently performing adds. private static AtomicInteger activeAddThreads; // The entry number counter for this job. private static AtomicInteger entryNumber; // The entry generator for this job. private static TemplateBasedEntryGenerator entryGenerator; // The rate limiter for this job. private static FixedRateBarrier rateLimiter; // The LDAP connection used by this thread. private LDAPConnection conn; // The parameters used by this job. private IntegerParameter firstNumberParameter = new IntegerParameter("first", "First Entry Number", "The value that should be used for the entry number for the first " + "entry generated.", true, 0, true, 0, false, 0); private IntegerParameter lastNumberParameter = new IntegerParameter("last", "Last Entry Number", "The value that should be used for the entry number for the last " + "entry generated. It must be greater than the first entry number", true, 1, true, 1, false, 0); private IntegerParameter maxRateParameter = new IntegerParameter("maxRate", "Max Operation Rate (Ops/Second)", "Specifies the maximum operation rate (in operations per second) to " + "attempt to maintain. A value less than or equal to zero " + "indicates that the client should attempt to process operations " + "as quickly as possible.", true, -1); private IntegerParameter rateLimitDurationParameter = new IntegerParameter( "maxRateDuration", "Max Rate Enforcement Interval (Seconds)", "Specifies the duration in seconds of the interval over which to " + "attempt to maintain the configured maximum rate. A value of " + "zero indicates that it should be equal to the statistics " + "collection interval. Large values may allow more variation but " + "may be more accurate over time. Small values can better " + "ensure that the rate doesn't exceed the requested level but may " + "be less able to achieve the desired rate.", true, 0, true,0, false, 0); private IntegerParameter thresholdParameter = new IntegerParameter( "threshold", "Response Time Threshold (ms)", "Specifies a threshold in milliseconds for which to count the number " + "of operations that take longer than this time to complete. A " + "value less than or equal to zero indicates that there will not " + "be any threshold.", false, -1); private IntegerParameter timeBetweenAddsAndDelsParameter = new IntegerParameter("timeBetweenAddsAndDels", "Time Between Adds and Deletes (s)", "The minimum length of time in seconds to wait after " + "completing all add operations before beginning the " + "deletes.", true, 30, true, 0, false, 0); private IntegerParameter timeBetweenRequestsParameter = new IntegerParameter( "timeBetweenRequests", "Time Between Requests (ms)", "The minimum length of time in milliseconds that should pass between " + "the beginning of one request and the beginning of the next.", false, 0, true, 0, false, 0); private MultiChoiceParameter processingTypeParameter = new MultiChoiceParameter("processingType", "Type of Processing to Perform", "The types of operations to perform, and the order in which " + "they should be processed.", PROCESSING_TYPES, PROCESSING_TYPE_ADDS_THEN_DELETES); private MultiLineTextParameter templateParameter = new MultiLineTextParameter( "templateLines", "Entry Template Lines", "The template that should be used to generate the entries to add.", DEFAULT_TEMPLATE_LINES, true); private StringParameter baseDNParameter = new StringParameter("baseDN", "Base DN", "The DN of the entry below which to perform the adds and deletes.", true, null); private StringParameter rdnAttributeParameter = new StringParameter("rdnAttr", "RDN Attribute", "The name of the attribute to use as the RDN attribute for entries " + "that are generated. It must be unique among all other entries " + "being generated and that may already exist in the server below " + "the base DN.", true, "uid"); /** * Creates a new instance of this job class. */ public LDAPAddAndDeleteRateJobClass() { templateParameter.setVisibleColumns(80); templateParameter.setVisibleRows(20); } /** * {@inheritDoc} */ @Override() public String getJobName() { return "LDAP Add and Delete Rate"; } /** * {@inheritDoc} */ @Override() public String getShortDescription() { return "Perform repeated LDAP add and delete operations"; } /** * {@inheritDoc} */ @Override() public String[] getLongDescription() { return new String[] { "This job may be used to perform repeated add and delete operations " + "against an LDAP directory server. The entries to be added will be " + "generated based on a user-defined template. The entries will be " + "deleted after all add processing has been completed and an optional " + "delay.", "It is possible to use this job to perform only add operations, only " + "delete operations, or both adds and deletes. If both add and delete " + "operations are to be performed, then it is possible to configure the " + "job so that it completes all add operations prior to processing any " + "of the deletes, or to delete each entry immediately after it has been " + "added." }; } /** * {@inheritDoc} */ @Override() public int overrideNumClients() { return 1; } /** * {@inheritDoc} */ @Override() protected List<Parameter> getNonLDAPParameterStubs() { return Arrays.asList( new PlaceholderParameter(), baseDNParameter, rdnAttributeParameter, firstNumberParameter, lastNumberParameter, new PlaceholderParameter(), templateParameter, new PlaceholderParameter(), processingTypeParameter, thresholdParameter, maxRateParameter, rateLimitDurationParameter, timeBetweenRequestsParameter, timeBetweenAddsAndDelsParameter); } /** * {@inheritDoc} */ @Override() public StatTracker[] getStatTrackerStubs(final String clientID, final String threadID, final int collectionInterval) { return new StatTracker[] { new IncrementalTracker(clientID, threadID, STAT_ADDS_COMPLETED, collectionInterval), new TimeTracker(clientID, threadID, STAT_ADD_DURATION, collectionInterval), new CategoricalTracker(clientID, threadID, STAT_ADD_RESULT_CODES, collectionInterval), new IncrementalTracker(clientID, threadID, STAT_ADDS_EXCEEDING_THRESHOLD, collectionInterval), new IncrementalTracker(clientID, threadID, STAT_DELETES_COMPLETED, collectionInterval), new TimeTracker(clientID, threadID, STAT_DELETE_DURATION, collectionInterval), new CategoricalTracker(clientID, threadID, STAT_DELETE_RESULT_CODES, collectionInterval), new IncrementalTracker(clientID, threadID, STAT_DELETES_EXCEEDING_THRESHOLD, collectionInterval) }; } /** * {@inheritDoc} */ @Override() public StatTracker[] getStatTrackers() { ArrayList<StatTracker> statList = new ArrayList<StatTracker>(8); if (performAdds) { statList.add(addsCompleted); statList.add(addTimer); statList.add(addResultCodes); if (responseTimeThreshold > 0) { statList.add(addsExceedingThreshold); } } if (performDeletes) { statList.add(deletesCompleted); statList.add(deleteTimer); statList.add(deleteResultCodes); if (responseTimeThreshold > 0) { statList.add(deletesExceedingThreshold); } } StatTracker[] trackers = new StatTracker[statList.size()]; return statList.toArray(trackers); } /** * {@inheritDoc} */ @Override() protected void validateNonLDAPJobInfo(final int numClients, final int threadsPerClient, final int threadStartupDelay, final Date startTime, final Date stopTime, final int duration, final int collectionInterval, final ParameterList parameters) throws InvalidValueException { // Make sure that neither a stop time nor a duration were specified. if ((stopTime != null) || (duration > 0)) { throw new InvalidValueException("Neither a stop time nor a duration " + "may be defined when scheduling this job."); } // Make sure that the last entry number is greater than the first entry // number. IntegerParameter firstParam = parameters.getIntegerParameter(firstNumberParameter.getName()); IntegerParameter lastParam = parameters.getIntegerParameter(lastNumberParameter.getName()); if (firstParam.getIntValue() >= lastParam.getIntValue()) { throw new InvalidValueException("The last entry number must be greater " + "than the first entry number."); } // Make sure that the template can be parsed. MultiLineTextParameter tmplParam = parameters.getMultiLineTextParameter(templateParameter.getName()); try { new TemplateBasedEntryGenerator(tmplParam.getNonBlankLines(), firstParam.getIntValue()); } catch (Exception e) { throw new InvalidValueException("Unable to parse the template: " + String.valueOf(e), e); } } /** * {@inheritDoc} */ @Override() protected boolean testNonLDAPJobParameters(final ParameterList parameters, final LDAPConnection connection, final ArrayList<String> outputMessages) { boolean successful = true; // Ensure that the base DN exists. StringParameter baseDNParam = parameters.getStringParameter(baseDNParameter.getName()); if ((baseDNParam != null) && baseDNParam.hasValue()) { try { String base = baseDNParam.getStringValue(); outputMessages.add("Ensuring that base entry '" + base + "' exists...."); SearchResultEntry e = connection.getEntry(base); if (e == null) { outputMessages.add("ERROR: The base entry does not exist."); successful = false; } else { outputMessages.add("The base entry exists."); } } catch (Exception e) { successful = false; outputMessages.add("Unable to perform the search: " + stackTraceToString(e)); } outputMessages.add(""); } return successful; } /** * {@inheritDoc} */ @Override() protected void initializeClientNonLDAP(final String clientID, final ParameterList parameters) throws UnableToRunException { parentRandom = new Random(); activeAddThreads = new AtomicInteger(getClientSideJob().getThreadsPerClient()); baseDNParameter = parameters.getStringParameter(baseDNParameter.getName()); baseDN = baseDNParameter.getStringValue(); rdnAttributeParameter = parameters.getStringParameter(rdnAttributeParameter.getName()); rdnAttribute = rdnAttributeParameter.getStringValue(); firstNumberParameter = parameters.getIntegerParameter(firstNumberParameter.getName()); firstEntryNumber = firstNumberParameter.getIntValue(); entryNumber = new AtomicInteger(firstEntryNumber); lastNumberParameter = parameters.getIntegerParameter(lastNumberParameter.getName()); lastEntryNumber = lastNumberParameter.getIntValue(); templateParameter = parameters.getMultiLineTextParameter(templateParameter.getName()); String[] templateLines = templateParameter.getNonBlankLines(); try { entryGenerator = new TemplateBasedEntryGenerator(templateLines, firstEntryNumber); } catch (Exception e) { throw new UnableToRunException("Unable to create the entry generator: " + String.valueOf(e), e); } alternateAddsAndDeletes = false; performAdds = true; performDeletes = true; processingTypeParameter = parameters.getMultiChoiceParameter( processingTypeParameter.getName()); if ((processingTypeParameter != null) && processingTypeParameter.hasValue()) { String val = processingTypeParameter.getStringValue(); if (val.equalsIgnoreCase(PROCESSING_TYPE_ALTERNATE_ADDS_AND_DELETES)) { alternateAddsAndDeletes = true; } else if (val.equalsIgnoreCase(PROCESSING_TYPE_ONLY_ADDS)) { performDeletes = false; } else if (val.equalsIgnoreCase(PROCESSING_TYPE_ONLY_DELETES)) { performAdds = false; } } responseTimeThreshold = -1; thresholdParameter = parameters.getIntegerParameter(thresholdParameter.getName()); if ((thresholdParameter != null) && thresholdParameter.hasValue()) { responseTimeThreshold = thresholdParameter.getIntValue(); } rateLimiter = null; maxRateParameter = parameters.getIntegerParameter(maxRateParameter.getName()); if ((maxRateParameter != null) && maxRateParameter.hasValue()) { int maxRate = maxRateParameter.getIntValue(); if (maxRate > 0) { int rateIntervalSeconds = 0; rateLimitDurationParameter = parameters.getIntegerParameter( rateLimitDurationParameter.getName()); if ((rateLimitDurationParameter != null) && rateLimitDurationParameter.hasValue()) { rateIntervalSeconds = rateLimitDurationParameter.getIntValue(); } if (rateIntervalSeconds <= 0) { rateIntervalSeconds = getClientSideJob().getCollectionInterval(); } rateLimiter = new FixedRateBarrier(rateIntervalSeconds * 1000L, maxRate * rateIntervalSeconds); } } timeBetweenRequests = 0L; timeBetweenRequestsParameter = parameters.getIntegerParameter(timeBetweenRequestsParameter.getName()); if ((timeBetweenRequestsParameter != null) && timeBetweenRequestsParameter.hasValue()) { timeBetweenRequests = timeBetweenRequestsParameter.getIntValue(); } timeBetweenAddsAndDeletes = 0L; timeBetweenAddsAndDelsParameter = parameters.getIntegerParameter( timeBetweenAddsAndDelsParameter.getName()); if ((timeBetweenAddsAndDelsParameter != null) && timeBetweenAddsAndDelsParameter.hasValue()) { timeBetweenAddsAndDeletes = 1000L * timeBetweenAddsAndDelsParameter.getIntValue(); } } /** * {@inheritDoc} */ @Override() public void initializeThread(final String clientID, final String threadID, final int collectionInterval, final ParameterList parameters) throws UnableToRunException { addsCompleted = new IncrementalTracker(clientID, threadID, STAT_ADDS_COMPLETED, collectionInterval); addTimer = new TimeTracker(clientID, threadID, STAT_ADD_DURATION, collectionInterval); addResultCodes = new CategoricalTracker(clientID, threadID, STAT_ADD_RESULT_CODES, collectionInterval); addsExceedingThreshold = new IncrementalTracker(clientID, threadID, STAT_ADDS_EXCEEDING_THRESHOLD, collectionInterval); deletesCompleted = new IncrementalTracker(clientID, threadID, STAT_DELETES_COMPLETED, collectionInterval); deleteTimer = new TimeTracker(clientID, threadID, STAT_DELETE_DURATION, collectionInterval); deleteResultCodes = new CategoricalTracker(clientID, threadID, STAT_DELETE_RESULT_CODES, collectionInterval); deletesExceedingThreshold = new IncrementalTracker(clientID, threadID, STAT_DELETES_EXCEEDING_THRESHOLD, collectionInterval); RealTimeStatReporter statReporter = getStatReporter(); if (statReporter != null) { String jobID = getJobID(); addsCompleted.enableRealTimeStats(statReporter, jobID); addTimer.enableRealTimeStats(statReporter, jobID); addsExceedingThreshold.enableRealTimeStats(statReporter, jobID); deletesCompleted.enableRealTimeStats(statReporter, jobID); deleteTimer.enableRealTimeStats(statReporter, jobID); deletesExceedingThreshold.enableRealTimeStats(statReporter, jobID); } random = new Random(parentRandom.nextLong()); try { conn = createConnection(); } catch (Exception e) { throw new UnableToRunException("Unable to establish a connection to " + "the target server: " + stackTraceToString(e), e); } } /** * {@inheritDoc} */ @Override() public void finalizeThread() { if (conn != null) { conn.close(); conn = null; } } /** * {@inheritDoc} */ @Override() public void runJob() { StringBuilder dnBuffer = new StringBuilder(); // Start collecting the appropriate set of statistics. if (performAdds) { addsCompleted.startTracker(); addTimer.startTracker(); addResultCodes.startTracker(); addsExceedingThreshold.startTracker(); } if (performDeletes && alternateAddsAndDeletes) { deletesCompleted.startTracker(); deleteTimer.startTracker(); deleteResultCodes.startTracker(); deletesExceedingThreshold.startTracker(); } if (performAdds) { while (! shouldStop()) { if (rateLimiter != null) { if (rateLimiter.await()) { continue; } } long addStartTime = System.currentTimeMillis(); int i = entryNumber.getAndIncrement(); if (i > lastEntryNumber) { break; } Entry entry; try { dnBuffer.setLength(0); dnBuffer.append(rdnAttribute); dnBuffer.append('='); dnBuffer.append(i); dnBuffer.append(','); dnBuffer.append(baseDN); entry = entryGenerator.createEntry(random, i, dnBuffer.toString()); } catch (SLAMDException se) { addResultCodes.increment(ResultCode.PARAM_ERROR.toString()); continue; } addTimer.startTimer(); try { LDAPResult addResult = conn.add(entry); addResultCodes.increment(addResult.getResultCode().toString()); } catch (LDAPException le) { addResultCodes.increment(le.getResultCode().toString()); } finally { addTimer.stopTimer(); addsCompleted.increment(); if ((responseTimeThreshold > 0) && (addTimer.getLastOperationTime() > responseTimeThreshold)) { addsExceedingThreshold.increment(); } } if (timeBetweenRequests > 0) { long elapsedTime = System.currentTimeMillis() - addStartTime; long sleepTime = timeBetweenRequests - elapsedTime; if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (Exception e) {} } } if (alternateAddsAndDeletes) { if (rateLimiter != null) { rateLimiter.await(); } long deleteStartTime = System.currentTimeMillis(); deleteTimer.startTimer(); try { LDAPResult deleteResult = conn.delete(entry.getDN()); deleteResultCodes.increment( deleteResult.getResultCode().toString()); } catch (LDAPException le) { deleteResultCodes.increment(le.getResultCode().toString()); } finally { deleteTimer.stopTimer(); deletesCompleted.increment(); if ((responseTimeThreshold > 0) && (deleteTimer.getLastOperationTime() > responseTimeThreshold)) { deletesExceedingThreshold.increment(); } } if (timeBetweenRequests > 0) { long elapsedTime = System.currentTimeMillis() - deleteStartTime; long sleepTime = timeBetweenRequests - elapsedTime; if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (Exception e) {} } } } } } if (performAdds) { addsCompleted.stopTracker(); addTimer.stopTracker(); addResultCodes.stopTracker(); addsExceedingThreshold.stopTracker(); if (alternateAddsAndDeletes) { deletesCompleted.stopTracker(); deleteTimer.stopTracker(); deleteResultCodes.stopTracker(); deletesExceedingThreshold.stopTracker(); } } if (performDeletes && (! alternateAddsAndDeletes)) { // Wait until all of the threads have completed their adds, and then sleep // if necessary before starting the deletes. if (performAdds) { int remainingActive = activeAddThreads.decrementAndGet(); if (remainingActive == 0) { if (timeBetweenAddsAndDeletes > 0) { try { Thread.sleep(timeBetweenAddsAndDeletes); } catch (Exception e) {} } entryNumber.set(firstEntryNumber); activeAddThreads.decrementAndGet(); } else { while (activeAddThreads.get() >= 0) { try { Thread.sleep(0, 1); } catch (Exception e) {} } } } // Perform all of the deletes. deletesCompleted.startTracker(); deleteTimer.startTracker(); deleteResultCodes.startTracker(); deletesExceedingThreshold.startTracker(); while (! shouldStop()) { if (rateLimiter != null) { if (rateLimiter.await()) { continue; } } long deleteStartTime = System.currentTimeMillis(); int i = entryNumber.getAndIncrement(); if (i > lastEntryNumber) { break; } dnBuffer.setLength(0); dnBuffer.append(rdnAttribute); dnBuffer.append('='); dnBuffer.append(i); dnBuffer.append(','); dnBuffer.append(baseDN); deleteTimer.startTimer(); try { LDAPResult deleteResult = conn.delete(dnBuffer.toString()); deleteResultCodes.increment(deleteResult.getResultCode().toString()); } catch (LDAPException le) { deleteResultCodes.increment(le.getResultCode().toString()); } finally { deleteTimer.stopTimer(); deletesCompleted.increment(); if ((responseTimeThreshold > 0) && (deleteTimer.getLastOperationTime() > responseTimeThreshold)) { deletesExceedingThreshold.increment(); } } if (timeBetweenRequests > 0) { long elapsedTime = System.currentTimeMillis() - deleteStartTime; long sleepTime = timeBetweenRequests - elapsedTime; if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (Exception e) {} } } } deletesCompleted.stopTracker(); deleteTimer.stopTracker(); deleteResultCodes.stopTracker(); deletesExceedingThreshold.stopTracker(); } } /** * {@inheritDoc} */ @Override() public synchronized void destroyThread() { if (conn != null) { conn.close(); conn = null; } } }