/*
* 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) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.jobgroup;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import com.slamd.asn1.ASN1Element;
import com.slamd.asn1.ASN1Integer;
import com.slamd.asn1.ASN1OctetString;
import com.slamd.asn1.ASN1Sequence;
import com.slamd.common.Constants;
import com.slamd.common.SLAMDException;
import com.slamd.db.DecodeException;
import com.slamd.job.Job;
import com.slamd.job.JobClass;
import com.slamd.job.JobItem;
import com.slamd.job.OptimizingJob;
import com.slamd.parameter.InvalidValueException;
import com.slamd.parameter.Parameter;
import com.slamd.parameter.ParameterList;
import com.slamd.server.SLAMDServer;
/**
* This class defines a data structure for holding information about a job that
* is scheduled as part of a job group.
*
*
* @author Neil A. Wilson
*/
public class JobGroupJob
implements JobGroupItem
{
/**
* The name of the encoded element that holds the job name.
*/
public static final String ELEMENT_NAME = "name";
/**
* The name of the encoded element that holds the job class name.
*/
public static final String ELEMENT_JOB_CLASS = "job_class";
/**
* The name of the encoded element that holds the statistics collection
* interval.
*/
public static final String ELEMENT_COLLECTION_INTERVAL =
"collection_interval";
/**
* The name of the encoded element that holds the job duration.
*/
public static final String ELEMENT_DURATION = "duration";
/**
* The name of the encoded element that holds the number of clients.
*/
public static final String ELEMENT_NUM_CLIENTS = "num_clients";
/**
* The name of the encoded element that holds the number of threads per
* client.
*/
public static final String ELEMENT_THREADS_PER_CLIENT = "threads_per_client";
/**
* The name of the encoded element that holds the thread startup delay.
*/
public static final String ELEMENT_THREAD_STARTUP_DELAY =
"thread_startup_delay";
/**
* The name of the encoded element that holds the job dependencies.
*/
public static final String ELEMENT_DEPENDENCIES = "dependencies";
/**
* The name of the encoded element that holds the set of mapped parameters.
*/
public static final String ELEMENT_MAPPED_PARAMS = "mapped_parameters";
/**
* The name of the encoded element that holds the set of fixed parameters.
*/
public static final String ELEMENT_FIXED_PARAMS = "fixed_parameters";
// The set of dependencies for this job. They will be the names of other jobs
// or optimizing jobs in the job group on which this job is dependent.
private ArrayList<String> dependencies;
// The job parameters that will be requested from the user, mapped from the
// names used in this job class to their names in the job group.
private LinkedHashMap<String,String> mappedParameters;
// The statistics collection interval for this job.
private int collectionInterval;
// The maximum length of time in seconds that this job should run.
private int duration;
// The number of clients to use to execute the job.
private int numClients;
// The number of threads per client to use to execute the job.
private int threadsPerClient;
// The thread startup delay in milliseconds for this job.
private int threadStartupDelay;
// The job class for this job.
private JobClass jobClass;
// The job group with which this job is associated.
private JobGroup jobGroup;
// The fixed-value parameters that will always be used for this job in the job
// group.
private ParameterList fixedParameters;
// A name used to identify this job in the job group.
private String name;
/**
* Creates a new job group job with the provided information.
*
* @param jobGroup The job group with which this job is
* associated.
* @param name The human-readable name for this job.
* @param jobClass The job class for this job.
* @param duration The duration for this job.
* @param collectionInterval The collection interval for this job.
* @param numClients The number of clients to use to run this job.
* @param threadsPerClient The number of threads per client to use to run
* this job.
* @param threadStartupDelay The thread startup delay in milliseconds for
* this job.
* @param dependencies The set of dependencies for this job.
* @param mappedParameters The set of mapped parameters for this job,
* mapped from the names used in the associated
* job class to the names used in the job group.
* @param fixedParameters The set of fixed parameters for this job.
*/
public JobGroupJob(JobGroup jobGroup, String name, JobClass jobClass,
int duration, int collectionInterval, int numClients,
int threadsPerClient, int threadStartupDelay,
ArrayList<String> dependencies,
LinkedHashMap<String,String> mappedParameters,
ParameterList fixedParameters)
{
this.jobGroup = jobGroup;
this.name = name;
this.jobClass = jobClass;
this.duration = duration;
this.collectionInterval = collectionInterval;
this.numClients = numClients;
this.threadsPerClient = threadsPerClient;
this.threadStartupDelay = threadStartupDelay;
this.dependencies = dependencies;
this.mappedParameters = mappedParameters;
this.fixedParameters = fixedParameters;
}
/**
* Retrieves the job group with which this job is associated.
*
* @return The job group with which this job is associated.
*/
public JobGroup getJobGroup()
{
return jobGroup;
}
/**
* Retrieves the human-readable name for this job.
*
* @return The human-readable name for this job.
*/
public String getName()
{
return name;
}
/**
* Specifies the human-readable name for this job.
*
* @param name The human-readable name for this job.
*/
public void setName(String name)
{
this.name = name;
}
/**
* Retrieves the job class for this job.
*
* @return The job class for this job.
*/
public JobClass getJobClass()
{
return jobClass;
}
/**
* Specifies the job class for this job.
*
* @param jobClass The job class for this job.
*/
public void setJobClass(JobClass jobClass)
{
this.jobClass = jobClass;
}
/**
* Retrieves the duration for this job.
*
* @return The duration for this job.
*/
public int getDuration()
{
return duration;
}
/**
* Specifies the duration for this job.
*
* @param duration The duration for this job.
*/
public void setDuration(int duration)
{
this.duration = duration;
}
/**
* Retrieves the collection interval for this job.
*
* @return The collection interval for this job.
*/
public int getCollectionInterval()
{
return collectionInterval;
}
/**
* Specifies the collection interval for this job.
*
* @param collectionInterval The collection interval for this job.
*/
public void setCollectionInterval(int collectionInterval)
{
this.collectionInterval = collectionInterval;
}
/**
* Retrieves the number of clients for this job.
*
* @return The number of clients for this job.
*/
public int getNumClients()
{
return numClients;
}
/**
* Specifies the number of clients for this job.
*
* @param numClients The number of clients for this job.
*/
public void setNumClients(int numClients)
{
this.numClients = numClients;
}
/**
* Retrieves the number of threads per client for this job.
*
* @return The number of threads per client for this job.
*/
public int getThreadsPerClient()
{
return threadsPerClient;
}
/**
* Specifies the number of threads per client for this job.
*
* @param threadsPerClient The number of threads per client for this job.
*/
public void setThreadsPerClient(int threadsPerClient)
{
this.threadsPerClient = threadsPerClient;
}
/**
* Retrieves the thread startup delay for this job.
*
* @return The thread startup delay for this job.
*/
public int getThreadStartupDelay()
{
return threadStartupDelay;
}
/**
* Specifies the thread startup delay for this job.
*
* @param threadStartupDelay The thread startup delay for this job.
*/
public void setThreadStartupDelay(int threadStartupDelay)
{
this.threadStartupDelay = threadStartupDelay;
}
/**
* Retrieves the set of dependencies for this job. The contents of the
* returned list may be altered by the caller.
*
* @return The set of dependencies for this job.
*/
public ArrayList<String> getDependencies()
{
return dependencies;
}
/**
* Retrieves the set of mapped parameters for this job, mapped from the names
* used in the associated job class to the names used in the job group. The
* contents of the returned map may be altered by the caller.
*
* @return The set of mapped parameters for this job.
*/
public LinkedHashMap<String,String> getMappedParameters()
{
return mappedParameters;
}
/**
* Retrieves the set of fixed parameters for this job. The contents of the
* returned parameter list may be altered by the caller.
*
* @return The set of fixed parameters for this job.
*/
public ParameterList getFixedParameters()
{
return fixedParameters;
}
/**
* Schedules this job for execution by the SLAMD server using the provided
* information.
*
* @param slamdServer The reference to the SLAMD server to use
* when scheduling the job.
* @param startTime The start time to use for the job.
* @param folderName The name of the folder into which the
* job should be placed.
* @param requestedClients The set of clients that have been
* requested for this job group.
* @param requestedMonitorClients the set of resource monitor clients that
* have been requested for this job group.
* @param monitorClientsIfAvailable Indicates whether the clients used to
* run the job should be monitored if there
* are also resource monitor clients
* running on the same system.
* @param externalDependencies A set of jobs outside of this job group
* on which the scheduled job should be
* dependent.
* @param parameters The set of parameters that the user
* provided for the job group.
* @param scheduledJobs The set of jobs and optimizing jobs that
* have been scheduled so far as part of
* the job group. They will be mapped from
* the name of the job or optimizing job in
* this job group to the corresponding
* object.
* @param messages A list of messages that should be
* displayed to the user as a result of
* scheduling the job.
*
* @throws SLAMDException If a problem occurs while attempting to schedule
* the job.
*/
public void schedule(SLAMDServer slamdServer, Date startTime,
String folderName, String[] requestedClients,
String[] requestedMonitorClients,
boolean monitorClientsIfAvailable,
String[] externalDependencies, ParameterList parameters,
LinkedHashMap<String,JobItem> scheduledJobs,
ArrayList<String> messages)
throws SLAMDException
{
// Create a new parameter list that combines the mapped parameters and the
// fixed parameters.
Parameter[] params = jobClass.getParameterStubs().clone().getParameters();
for (int i=0; i < params.length; i++)
{
String paramName = params[i].getName();
String mappedName = mappedParameters.get(paramName);
if (mappedName == null)
{
Parameter p = fixedParameters.getParameter(paramName);
if (p != null)
{
params[i].setValueFrom(p);
}
}
else
{
Parameter p = parameters.getParameter(mappedName);
if (p != null)
{
params[i].setValueFrom(p);
}
}
}
ParameterList jobParameters = new ParameterList(params);
// Create the set of dependencies for the job.
ArrayList<String> depList = new ArrayList<String>(dependencies.size());
if ((externalDependencies != null) && (externalDependencies.length > 0))
{
for (int i=0; i < externalDependencies.length; i++)
{
depList.add(externalDependencies[i]);
}
}
for (int i=0; i < dependencies.size(); i++)
{
String dependencyName = dependencies.get(i);
Object o = scheduledJobs.get(dependencyName);
if (o == null)
{
continue;
}
else if (o instanceof Job)
{
depList.add(((Job) o).getJobID());
}
else if (o instanceof OptimizingJob)
{
depList.add(((OptimizingJob) o).getOptimizingJobID());
}
}
String[] dependencyArray = new String[depList.size()];
depList.toArray(dependencyArray);
// Execute the validateJobInfo method for the job class. This is necessary
// to ensure that certain background processing that might be required gets
// done.
try
{
jobClass.validateJobInfo(numClients, threadsPerClient, threadStartupDelay,
startTime, null, duration, collectionInterval,
jobParameters);
}
catch (InvalidValueException ive)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ive));
messages.add("WARNING: validateJobInfo for job " + name +
" failed with message: " + ive.getMessage());
}
// Create the job using the information available.
Job job = new Job(slamdServer, jobClass.getClass().getName(), numClients,
threadsPerClient, threadStartupDelay, startTime, null,
duration, collectionInterval, jobParameters, false);
job.setJobDescription(name);
job.setJobGroup(jobGroup.getName());
job.setDependencies(dependencyArray);
job.setFolderName(folderName);
job.setMonitorClientsIfAvailable(monitorClientsIfAvailable);
job.setWaitForClients(true);
if ((requestedClients != null) && (requestedClients.length > 0))
{
// FIXME -- Do we need to worry about the possibility of jobs running in
// parallel within this job group?
ArrayList<String> clientList = new ArrayList<String>(numClients);
for (int i=0; ((i < numClients) && (i < requestedClients.length)); i++)
{
clientList.add(requestedClients[i]);
}
String[] clientArray = new String[clientList.size()];
clientList.toArray(clientArray);
job.setRequestedClients(clientArray);
}
if ((requestedMonitorClients != null) &&
(requestedMonitorClients.length > 0))
{
job.setResourceMonitorClients(requestedMonitorClients);
}
// Schedule the job for execution.
slamdServer.getScheduler().scheduleJob(job, folderName);
messages.add("Successfully scheduled job " + name + " with job ID " +
job.getJobID());
// Add the job to the job hash so it can be used as a dependency for other
// jobs.
scheduledJobs.put(name, job);
}
/**
* Encodes the information in this job group job to an ASN.1 element suitable
* for use in an encoded job group.
*
* @return The ASN.1 element containing the encoded job information.
*/
public ASN1Element encode()
{
ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>();
elementList.add(new ASN1OctetString(ELEMENT_NAME));
elementList.add(new ASN1OctetString(name));
elementList.add(new ASN1OctetString(ELEMENT_JOB_CLASS));
elementList.add(new ASN1OctetString(jobClass.getClass().getName()));
elementList.add(new ASN1OctetString(ELEMENT_COLLECTION_INTERVAL));
elementList.add(new ASN1Integer(collectionInterval));
if (duration > 0)
{
elementList.add(new ASN1OctetString(ELEMENT_DURATION));
elementList.add(new ASN1Integer(duration));
}
elementList.add(new ASN1OctetString(ELEMENT_NUM_CLIENTS));
elementList.add(new ASN1Integer(numClients));
elementList.add(new ASN1OctetString(ELEMENT_THREADS_PER_CLIENT));
elementList.add(new ASN1Integer(threadsPerClient));
if (threadStartupDelay > 0)
{
elementList.add(new ASN1OctetString(ELEMENT_THREAD_STARTUP_DELAY));
elementList.add(new ASN1Integer(threadStartupDelay));
}
if ((dependencies != null) && (! dependencies.isEmpty()))
{
ArrayList<ASN1Element> depElements =
new ArrayList<ASN1Element>(dependencies.size());
for (int i=0; i < dependencies.size(); i++)
{
depElements.add(new ASN1OctetString(dependencies.get(i)));
}
elementList.add(new ASN1OctetString(ELEMENT_DEPENDENCIES));
elementList.add(new ASN1Sequence(depElements));
}
if ((mappedParameters != null) && (! mappedParameters.isEmpty()))
{
ArrayList<ASN1Element> paramElements = new ArrayList<ASN1Element>();
Iterator<String> iterator = mappedParameters.keySet().iterator();
while (iterator.hasNext())
{
String jobName = iterator.next();
String groupName = mappedParameters.get(jobName);
ASN1Element[] paramElementArray =
{
new ASN1OctetString(jobName),
new ASN1OctetString(groupName)
};
paramElements.add(new ASN1Sequence(paramElementArray));
}
elementList.add(new ASN1OctetString(ELEMENT_MAPPED_PARAMS));
elementList.add(new ASN1Sequence(paramElements));
}
if (fixedParameters != null)
{
elementList.add(new ASN1OctetString(ELEMENT_FIXED_PARAMS));
elementList.add(fixedParameters.encode());
}
return new ASN1Sequence(elementList);
}
/**
* Decodes the information in the provided element as a job group job.
*
* @param slamdServer The SLAMD server instance to use in the decoding
* process.
* @param jobGroup The job group with which this job is associated.
* @param encodedJob The encoded job information to decode.
*
* @return The decoded job group job.
*
* @throws DecodeException If a problem occurs while attempting to decode
* the job information.
*/
public static JobGroupJob decode(SLAMDServer slamdServer, JobGroup jobGroup,
ASN1Element encodedJob)
throws DecodeException
{
try
{
ArrayList<String> dependencies = new ArrayList<String>();
LinkedHashMap<String,String> mappedParameters =
new LinkedHashMap<String,String>();
int collectionInterval = Constants.DEFAULT_COLLECTION_INTERVAL;
int duration = -1;
int numClients = -1;
int threadsPerClient = -1;
int threadStartupDelay = 0;
JobClass jobClass = null;
ParameterList fixedParameters = new ParameterList();
String name = null;
ASN1Element[] elements = encodedJob.decodeAsSequence().getElements();
for (int i=0; i < elements.length; i += 2)
{
String elementName = elements[i].decodeAsOctetString().getStringValue();
if (elementName.equals(ELEMENT_NAME))
{
name = elements[i+1].decodeAsOctetString().getStringValue();
}
else if (elementName.equals(ELEMENT_JOB_CLASS))
{
// FIXME -- Does this need to be able to handle classes that aren't
// registered?
String jobClassName =
elements[i+1].decodeAsOctetString().getStringValue();
jobClass = slamdServer.getJobClass(jobClassName);
}
else if (elementName.equals(ELEMENT_COLLECTION_INTERVAL))
{
collectionInterval = elements[i+1].decodeAsInteger().getIntValue();
}
else if (elementName.equals(ELEMENT_DURATION))
{
duration = elements[i+1].decodeAsInteger().getIntValue();
}
else if (elementName.equals(ELEMENT_NUM_CLIENTS))
{
numClients = elements[i+1].decodeAsInteger().getIntValue();
}
else if (elementName.equals(ELEMENT_THREADS_PER_CLIENT))
{
threadsPerClient = elements[i+1].decodeAsInteger().getIntValue();
}
else if (elementName.equals(ELEMENT_THREAD_STARTUP_DELAY))
{
threadStartupDelay = elements[i+1].decodeAsInteger().getIntValue();
}
else if (elementName.equals(ELEMENT_DEPENDENCIES))
{
ASN1Element[] depElements =
elements[i+1].decodeAsSequence().getElements();
for (int j=0; j < depElements.length; j++)
{
dependencies.add(
depElements[j].decodeAsOctetString().getStringValue());
}
}
else if (elementName.equals(ELEMENT_MAPPED_PARAMS))
{
ASN1Element[] paramElements =
elements[i+1].decodeAsSequence().getElements();
for (int j=0; j < paramElements.length; j++)
{
ASN1Element[] pElements =
paramElements[j].decodeAsSequence().getElements();
mappedParameters.put(
pElements[0].decodeAsOctetString().getStringValue(),
pElements[1].decodeAsOctetString().getStringValue());
}
}
else if (elementName.equals(ELEMENT_FIXED_PARAMS))
{
fixedParameters = ParameterList.decode(elements[i+1]);
}
}
return new JobGroupJob(jobGroup, name, jobClass, duration,
collectionInterval, numClients, threadsPerClient,
threadStartupDelay, dependencies, mappedParameters,
fixedParameters);
}
catch (Exception e)
{
throw new DecodeException("Unable to decode the job group job: " + e, e);
}
}
}