blob: ae1e49b0bb59a15a7348b6e7183b184238ef4a08 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.server.benchmarks;
import java.util.ArrayList;
import java.util.Date;
import netscape.ldap.LDAPConnection;
import netscape.ldap.LDAPConstraints;
import netscape.ldap.LDAPException;
import com.sun.slamd.job.JobClass;
import com.sun.slamd.job.UnableToRunException;
import com.sun.slamd.parameter.BooleanParameter;
import com.sun.slamd.parameter.IntegerParameter;
import com.sun.slamd.parameter.InvalidValueException;
import com.sun.slamd.parameter.Parameter;
import com.sun.slamd.parameter.ParameterList;
import com.sun.slamd.parameter.PasswordParameter;
import com.sun.slamd.parameter.PlaceholderParameter;
import com.sun.slamd.parameter.StringParameter;
import com.sun.slamd.stat.IncrementalTracker;
import com.sun.slamd.stat.RealTimeStatReporter;
import com.sun.slamd.stat.StatTracker;
import com.sun.slamd.stat.TimeTracker;
/**
* A simple bind benchmark. Here the same bindDn and password is
* used to bind to the directory. The connection to the directory is
* by default shared across iterations of a thread but this can be
* changed. This is not a real world experiment but a way for us to
* stress test the server, profile it, optimize it and regression test
* our results under stress for more reliable feedback.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class BindBenchmark extends JobClass
{
/**
* The name of the stat tracker that will be used to count the number of
* authentication attempts.
*/
public static final String STAT_TRACKER_AUTHENTICATION_ATTEMPTS = "Authentication Attempts";
/**
* The name of the stat tracker that will be used to keep track of the time
* required to perform each authentication.
*/
public static final String STAT_TRACKER_AUTHENTICATION_TIME = "Authentication Time";
/**
* The name of the stat tracker that will be used to count the number of
* failed authentications.
*/
public static final String STAT_TRACKER_FAILED_AUTHENTICATIONS = "Failed Authentications";
/**
* The name of the stat tracker that will be used to count the number of
* successful authentications.
*/
public static final String STAT_TRACKER_SUCCESSFUL_AUTHENTICATIONS = "Successful Authentications";
// -----------------------------------------------------------------------
// Extracted paramter values for the whole Job
// -----------------------------------------------------------------------
// The connection is estabilished anew every time for each bind request
// if this option is not selected instead of reusing the existing connection
// for each iteration. The default is to share the connection.
static boolean shareConnections;
// Indicates whether bind failures because of invalid credentials will be
// ignored (so we can optimize for authn failures as well).
static boolean ignoreInvalidCredentials;
// The maximum number of iterations per thread.
static int iterations;
// The maximum length of time that any single LDAP operation will be allowed
// to take before it is cancelled.
static int timeLimit;
// The time to start working before beginning statistics collection.
static int warmUpTime;
// The delay in milliseconds between authentication attempts.
static long delay;
// The port number of the directory server.
static int directoryPort;
// The address of the directory server.
static String directoryHost;
// The DN to use to bind to the directory
static String bindDN;
// The password for the bind DN.
static String bindPW;
// -----------------------------------------------------------------------
// Paramters definitions
// -----------------------------------------------------------------------
// The parameter controlling where or not connections are reused
BooleanParameter shareConnectionsParameter = new BooleanParameter( "shareConnectionsParameter",
"Share Connections",
"Specifies whether or not the connection to the LDAP server is shared or " +
"re-estabilished every time for each iteration. If true the iteration creates" +
"and destroys a new connection before issueing a bind request.", true );
// The parameter that indicates the delay that should be used between each
// authentication attempt.
IntegerParameter delayParameter = new IntegerParameter( "delay", "Time Between Authentications (ms)",
"Specifies the length of time in milliseconds " + "each thread should wait between authentication "
+ "attempts. Note that this delay will be " + "between the starts of consecutive attempts and "
+ "not between the end of one attempt and the " + "beginning of the next. If an authentication "
+ "takes longer than this length of time, then " + "there will be no delay.", true, 0, true, 0, false, 0 );
// The parameter that indicates the number of iterations to perform.
IntegerParameter iterationsParameter = new IntegerParameter( "num_iterations", "Number of Iterations",
"The number of authentications that should be " + "performed by each thread", false, -1 );
// The parameter used to indicate the maximum length of time that any single
// LDAP operation will be allowed to take.
IntegerParameter timeLimitParameter = new IntegerParameter( "time_limit", "Operation Time Limit",
"The maximum length of time in seconds that any " + "single LDAP operation will be allowed to take "
+ "before it is cancelled.", true, 0, true, 0, false, 0 );
// The parmeter that specifies the cool-down time in seconds.
IntegerParameter warmUpParameter = new IntegerParameter( "warm_up", "Warm Up Time",
"The time in seconds that the job should " + "search before beginning statistics collection.", true, 0, true,
0, false, 0 );
// The placeholder parameter used as a spacer in the admin interface.
PlaceholderParameter placeholder = new PlaceholderParameter();
// The parameter used to indicate the port number for the directory server.
IntegerParameter portParameter = new IntegerParameter( "ldap_port", "Directory Server Port",
"The port number for the directory server.", true, 389, true, 1, true, 65535 );
// The parameter used to indicate the address of the directory server.
StringParameter hostParameter = new StringParameter( "ldap_host", "Directory Server Address",
"The address for the directory server.", true, "" );
// The parameter used to indicate the bind DN.
StringParameter bindDNParameter = new StringParameter( "binddn", "Directory Bind DN",
"The DN to use when binding to the directory server.", false, "" );
// The parameter used to indicate the bind DN.
PasswordParameter bindPWParameter = new PasswordParameter( "bindpw", "Bind Password",
"The password to use when binding.", false, "" );
// -----------------------------------------------------------------------
// Stat trakers for each thread.
// -----------------------------------------------------------------------
// The stat tracker that will count the # of authentication attempts.
IncrementalTracker attemptCounter;
// The stat tracker that will count the # of failed authentications.
IncrementalTracker failureCounter;
// The stat tracker that will count the # of successful authentications.
IncrementalTracker successCounter;
// The stat tracker that will time each authentication.
TimeTracker authTimer;
// -----------------------------------------------------------------------
// Connection and other parameters for each thread
// -----------------------------------------------------------------------
// The LDAP connection that will be used for bind operations by this thread.
LDAPConnection bindConnection;
// The set of constraints that will be used for bind operations.
LDAPConstraints bindConstraints;
public String getJobDescription()
{
return "Does a bind using a single user name then immediately unbinds.";
}
public String getJobName()
{
return "Bind/Unbind Optimization Test";
}
public String getJobCategoryName()
{
return "ApacheDS Optimization Tests";
}
/**
* Returns the set of parameters whose value may be specified by the end user.
*
* @return The set of configurable parameters for this job class.
*/
public ParameterList getParameterStubs()
{
Parameter[] parameterArray = new Parameter[]
{ placeholder, hostParameter, portParameter, bindDNParameter, bindPWParameter, placeholder,
warmUpParameter, timeLimitParameter, delayParameter, placeholder,
iterationsParameter, shareConnectionsParameter };
return new ParameterList( parameterArray );
}
public StatTracker[] getStatTrackerStubs( String clientID, String threadID, int collectionInterval )
{
return new StatTracker[]
{
new IncrementalTracker( clientID, threadID, STAT_TRACKER_AUTHENTICATION_ATTEMPTS, collectionInterval ),
new IncrementalTracker( clientID, threadID, STAT_TRACKER_SUCCESSFUL_AUTHENTICATIONS, collectionInterval ),
new IncrementalTracker( clientID, threadID, STAT_TRACKER_FAILED_AUTHENTICATIONS, collectionInterval ),
new TimeTracker( clientID, threadID, STAT_TRACKER_AUTHENTICATION_TIME, collectionInterval ) };
}
public StatTracker[] getStatTrackers()
{
return new StatTracker[]
{ attemptCounter, successCounter, failureCounter, authTimer };
}
public void validateJobInfo( int numClients, int threadsPerClient, int threadStartupDelay, Date startTime,
Date stopTime, int duration, int collectionInterval, ParameterList parameters ) throws InvalidValueException
{
// might want to add something here later
}
public boolean providesParameterTest()
{
return true;
}
/**
* Provides a means of testing the provided job parameters to determine
* whether they are valid (e.g., to see if the server is reachable) before
* scheduling the job for execution. This method will be executed by the
* SLAMD server system itself and not by any of the clients.
*
* @param parameters The job parameters to be tested.
* @param outputMessages The lines of output that were generated as part of
* the testing process. Each line of output should
* be added to this list as a separate string, and
* empty strings (but not <CODE>null</CODE> values)
* are allowed to provide separation between
* different messages. No formatting should be
* provided for these messages, however, since they
* may be displayed in either an HTML or plain text
* interface.
*
* @return <CODE>true</CODE> if the test completed successfully, or
* <CODE>false</CODE> if not.
*/
public boolean testJobParameters( ParameterList parameters, ArrayList outputMessages )
{
// Get all the parameters that we might need to perform the test.
StringParameter hostParam = parameters.getStringParameter( hostParameter.getName() );
if ( ( hostParam == null ) || ( !hostParam.hasValue() ) )
{
outputMessages.add( "ERROR: No directory server address was provided." );
return false;
}
String host = hostParam.getStringValue();
IntegerParameter portParam = parameters.getIntegerParameter( portParameter.getName() );
if ( ( portParam == null ) || ( !hostParam.hasValue() ) )
{
outputMessages.add( "ERROR: No directory server port was provided." );
return false;
}
int port = portParam.getIntValue();
String bindDN = "";
StringParameter bindDNParam = parameters.getStringParameter( bindDNParameter.getName() );
if ( ( bindDNParam != null ) && bindDNParam.hasValue() )
{
bindDN = bindDNParam.getStringValue();
}
String bindPassword = "";
PasswordParameter bindPWParam = parameters.getPasswordParameter( bindPWParameter.getName() );
if ( ( bindPWParam != null ) && bindPWParam.hasValue() )
{
bindPassword = bindPWParam.getStringValue();
}
// Create the LDAPConnection object that we will use to communicate with the directory server.
LDAPConnection conn = new LDAPConnection();
// Attempt to establish a connection to the directory server.
try
{
outputMessages.add( "Attempting to establish a connection to " + host + ":" + port + "...." );
conn.connect( host, port );
outputMessages.add( "Connected successfully." );
outputMessages.add( "" );
}
catch ( Exception e )
{
outputMessages.add( "ERROR: Unable to connect to the directory " + "server: " + stackTraceToString( e ) );
return false;
}
// Attempt to bind to the directory server using the bind DN and password.
try
{
outputMessages.add( "Attempting to perform an LDAPv3 bind to the " + "directory server with a DN of '"
+ bindDN + "'...." );
conn.bind( 3, bindDN, bindPassword );
outputMessages.add( "Bound successfully." );
outputMessages.add( "" );
}
catch ( Exception e )
{
try
{
conn.disconnect();
}
catch ( Exception e2 )
{
}
outputMessages.add( "ERROR: Unable to bind to the directory server: " + stackTraceToString( e ) );
return false;
}
// At this point, all tests have passed. Close the connection and return true.
try
{
conn.disconnect();
}
catch ( Exception e )
{
}
outputMessages.add( "All tests completed successfully." );
return true;
}
/**
* Performs initialization for this job on each client immediately before each
* thread is created to actually run the job.
*
* @param clientID The ID assigned to the client running this job.
* @param parameters The set of parameters provided to this job that can be
* used to customize its behavior.
*
* @throws UnableToRunException If the client initialization could not be
* completed successfully and the job is unable
* to run.
*/
public void initializeClient( String clientID, ParameterList parameters ) throws UnableToRunException
{
// Get the shareConnections boolean parameter
shareConnectionsParameter = parameters.getBooleanParameter( shareConnectionsParameter.getName() );
if ( hostParameter == null )
{
shareConnections = true; // the default
}
else
{
shareConnections = shareConnectionsParameter.getBooleanValue();
}
// Get the directory server address
hostParameter = parameters.getStringParameter( hostParameter.getName() );
if ( hostParameter == null )
{
throw new UnableToRunException( "No directory server host provided." );
}
else
{
directoryHost = hostParameter.getStringValue();
}
// Get the directory server port
portParameter = parameters.getIntegerParameter( portParameter.getName() );
if ( portParameter != null )
{
directoryPort = portParameter.getIntValue();
}
// Get the DN to use to bind to the directory server.
bindDNParameter = parameters.getStringParameter( bindDNParameter.getName() );
if ( bindDNParameter == null )
{
bindDN = "";
}
else
{
bindDN = bindDNParameter.getStringValue();
}
// Get the password to use to bind to the directory server.
bindPWParameter = parameters.getPasswordParameter( bindPWParameter.getName() );
if ( bindPWParameter == null )
{
bindPW = "";
}
else
{
bindPW = bindPWParameter.getStringValue();
}
// Get the warm up time.
warmUpTime = 0;
warmUpParameter = parameters.getIntegerParameter( warmUpParameter.getName() );
if ( warmUpParameter != null )
{
warmUpTime = warmUpParameter.getIntValue();
}
// Get the max operation time limit.
timeLimitParameter = parameters.getIntegerParameter( timeLimitParameter.getName() );
if ( timeLimitParameter != null )
{
timeLimit = timeLimitParameter.getIntValue();
}
// Get the delay between authentication attempts.
delay = 0;
delayParameter = parameters.getIntegerParameter( delayParameter.getName() );
if ( delayParameter != null )
{
delay = delayParameter.getIntValue();
}
// Get the number of iterations to perform.
iterations = -1;
iterationsParameter = parameters.getIntegerParameter( iterationsParameter.getName() );
if ( ( iterationsParameter != null ) && ( iterationsParameter.hasValue() ) )
{
iterations = iterationsParameter.getIntValue();
}
}
public void initializeThread( String clientID, String threadID, int collectionInterval, ParameterList parameters )
throws UnableToRunException
{
if ( shareConnections )
{
bindConnection = new LDAPConnection();
try
{
bindConnection.connect( 3, directoryHost, directoryPort, "", "" );
}
catch ( Exception e )
{
throw new UnableToRunException( "Unable to establish the connections " + "to the directory server: " + e,
e );
}
// Initialize the constraints.
bindConstraints = bindConnection.getConstraints();
bindConstraints.setTimeLimit( 1000 * timeLimit );
}
// Create the stat trackers.
attemptCounter = new IncrementalTracker( clientID, threadID, STAT_TRACKER_AUTHENTICATION_ATTEMPTS,
collectionInterval );
successCounter = new IncrementalTracker( clientID, threadID, STAT_TRACKER_SUCCESSFUL_AUTHENTICATIONS,
collectionInterval );
failureCounter = new IncrementalTracker( clientID, threadID, STAT_TRACKER_FAILED_AUTHENTICATIONS,
collectionInterval );
authTimer = new TimeTracker( clientID, threadID, STAT_TRACKER_AUTHENTICATION_TIME, collectionInterval );
// Enable real-time reporting of the data for these stat trackers.
RealTimeStatReporter statReporter = getStatReporter();
if ( statReporter != null )
{
String jobID = getJobID();
attemptCounter.enableRealTimeStats( statReporter, jobID );
successCounter.enableRealTimeStats( statReporter, jobID );
failureCounter.enableRealTimeStats( statReporter, jobID );
authTimer.enableRealTimeStats( statReporter, jobID );
}
}
/**
* Performs the work of actually running the job. When this method completes,
* the job will be done.
*/
public void runJob()
{
// Determine the range of time for which we should collect statistics.
long currentTime = System.currentTimeMillis();
boolean collectingStats = false;
long startCollectingTime = currentTime + ( 1000 * warmUpTime );
long stopCollectingTime = Long.MAX_VALUE;
// See if this thread should operate "infinitely" (i.e., not a fixed number of iterations)
boolean infinite = ( iterations <= 0 );
// Loop until it is time to stop.
for ( int ii = 0; !shouldStop() && ( infinite || ii < iterations ); ii++ )
{
currentTime = System.currentTimeMillis();
if ( ( !collectingStats ) && ( currentTime >= startCollectingTime ) && ( currentTime < stopCollectingTime ) )
{
// Start all the stat trackers.
attemptCounter.startTracker();
successCounter.startTracker();
failureCounter.startTracker();
authTimer.startTracker();
collectingStats = true;
}
else if ( ( collectingStats ) && ( currentTime >= stopCollectingTime ) )
{
// Stop all the stat trackers.
attemptCounter.stopTracker();
successCounter.stopTracker();
failureCounter.stopTracker();
authTimer.stopTracker();
collectingStats = false;
}
// See if we need to sleep before the next attempt
if ( delay > 0 )
{
long now = System.currentTimeMillis();
long sleepTime = delay - now;
if ( sleepTime > 0 )
{
try
{
Thread.sleep( sleepTime );
}
catch ( InterruptedException ie )
{
}
if ( shouldStop() )
{
break;
}
}
}
if ( ! shareConnections )
{
bindConnection = new LDAPConnection();
try
{
bindConnection.connect( 3, directoryHost, directoryPort, "", "" );
}
catch ( Exception e )
{
throw new IllegalStateException( "Unable to establish the connections "
+ "to the directory server: " + e, e );
}
// Initialize the constraints.
bindConstraints = bindConnection.getConstraints();
bindConstraints.setTimeLimit( 1000 * timeLimit );
}
if ( collectingStats )
{
attemptCounter.increment();
authTimer.startTimer();
}
// Increment the number of authentication attempts and start the timer
try
{
// Perform a bind as the user to verify that the provided password is
// valid.
bindConnection.authenticate( 3, bindDN, bindPW );
if ( collectingStats )
{
successCounter.increment();
authTimer.stopTimer();
}
}
catch ( LDAPException le )
{
if ( !( ignoreInvalidCredentials && ( le.getLDAPResultCode() == LDAPException.INVALID_CREDENTIALS ) ) )
{
if ( collectingStats )
{
failureCounter.increment();
authTimer.stopTimer();
}
}
StringBuffer buf = new StringBuffer();
buf.append( "LDAPException: " ).append( le.getMessage() )
.append( " - " ).append( le.getLDAPErrorMessage() );
writeVerbose( buf.toString() );
}
finally
{
if ( ! shareConnections )
{
if ( bindConnection != null )
{
try
{
bindConnection.disconnect();
}
catch ( Exception e )
{
}
bindConnection = null;
}
}
}
}
attemptCounter.stopTracker();
successCounter.stopTracker();
failureCounter.stopTracker();
authTimer.stopTracker();
}
/**
* Attempts to force this thread to exit by closing the connections to the
* directory server and setting them to <CODE>null</CODE>.
*/
public void destroy()
{
if ( shareConnections )
{
if ( bindConnection != null )
{
try
{
bindConnection.disconnect();
}
catch ( Exception e )
{
}
bindConnection = null;
}
}
}
}