/**
* Copyright 2014 SAP AG
*
* Licensed 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.spotter.ext.workload.simple;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.lpe.common.config.GlobalConfiguration;
import org.lpe.common.extension.IExtension;
import org.lpe.common.util.system.LpeSystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spotter.core.workload.AbstractWorkloadAdapter;
import org.spotter.core.workload.LoadConfig;
import org.spotter.exceptions.WorkloadException;
/**
* Generates a simple closed workload.
*
* @author Alexander Wert
*/
public class SimpleWorkloadDriver extends AbstractWorkloadAdapter {
private static final long _1000L = 1000L;
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleWorkloadDriver.class);
/**
* The path configuration key to the user script path.
*/
public static final String USER_SCRIPT_PATH = "org.spotter.workload.simple.userScriptPath";
/**
* The configuration key to the user script class name.
*/
public static final String USER_SCRIPT_CLASS_NAME = "org.spotter.workload.simple.userScriptClassName";
/**
* The monitor helps to enable the possible for others threads, to passivly
* wait (warmUpMonitor.wait()) for this thread till the warm-up phase is
* finished. Otherwise, buzy waiting needs to be used.
*/
private final Object warmUpMonitor = new Object();
/**
* The monitor helps to enable the possible for others threads, to passivly
* wait (experimentMonitor.wait()) for this thread till the experimentation
* phase is finished. Otherwise, buzy waiting needs to be used.
*/
private final Object experimentMonitor = new Object();
/**
* True, when the warm-up / ramp-up phase for the experiment has been
* finished.
*/
private boolean warmupPhaseFinished;
/**
* True, when the warm-up phase + experiment phase has been finished.
*/
private boolean experimentPhaseFinished;
private URLClassLoader urlClassLoader;
private Future<?> workloadGenerationJob;
/**
* The number of (virtual) users currently in the system. This variable
* should be accessed in a syncronized way.
*/
private int numActiveUsers = 0;
/**
* Constructor.
*
* @param provider
* extension provider
*/
public SimpleWorkloadDriver(IExtension<?> provider) {
super(provider);
}
@Override
public void startLoad(final LoadConfig loadConfig) throws WorkloadException {
// reset current variables
warmupPhaseFinished = false;
experimentPhaseFinished = false;
numActiveUsers = 0;
final Object wlDriver = this;
Runnable task = new Runnable() {
@Override
public void run() {
try {
// load the global configuration and overwrite local
// configration properties
getProperties().putAll(GlobalConfiguration.getInstance().getProperties());
checkProperties(getProperties());
int numberUsers = loadConfig.getNumUsers();
LOGGER.info("starting " + numberUsers + " vUsers ...");
File userScriptFile = new File(getProperties().getProperty(USER_SCRIPT_PATH));
String userScriptClassNAme = getProperties().getProperty(USER_SCRIPT_CLASS_NAME);
final long experimentDuration = loadConfig.getExperimentDuration() * _1000L; // [ms]
// load one virutal user
Class<?> vUserClass;
vUserClass = loadVUserScript(userScriptFile, userScriptClassNAme);
// we need to keep track for the offset for the cooldown of
// the
// virtual user, we
// are executing every loop
int timeOffsetMultiplicatorCoolDown = 0;
for (int i = 0; i < numberUsers; i++) {
if ((i + 1) % loadConfig.getCoolDownUsersPerInterval() == 0) {
timeOffsetMultiplicatorCoolDown++;
}
startVUser(vUserClass, loadConfig.getCoolDownIntervalLength() * timeOffsetMultiplicatorCoolDown);
// We put "rampUpUsersPerInterval" into the system. When
// we
// have added all of them in one
// interval, we set the WorkloadDriver to sleep.
// Afterwards the loop is going to continue the next
// users
// for the next ramp up interval.
if ((i + 1) % loadConfig.getRampUpUsersPerInterval() == 0) {
sleep(loadConfig.getRampUpIntervalLength());
}
}
// Some thread could be waiting for us, till the warm-up
// phase
// is finished. Notify them!
synchronized (warmUpMonitor) {
warmupPhaseFinished = true;
warmUpMonitor.notifyAll();
}
// In the experimentation time, the users are just executing
// their task. In this time
// system bottlenecks might get visible
sleep(experimentDuration);
// Some thread could be waiting for us, till the experiment
// phase is finished. Notify them!
synchronized (experimentMonitor) {
experimentPhaseFinished = true;
experimentMonitor.notifyAll();
}
LOGGER.info("Simple load with " + numberUsers + " vUsers terminated");
} catch (Throwable e) {
synchronized (warmUpMonitor) {
warmupPhaseFinished = true;
warmUpMonitor.notifyAll();
}
synchronized (experimentMonitor) {
experimentPhaseFinished = true;
experimentMonitor.notifyAll();
}
synchronized (wlDriver) {
LOGGER.debug("notifying all...");
wlDriver.notifyAll();
}
throw new RuntimeException(e);
}
}
};
workloadGenerationJob = LpeSystemUtils.submitTask(task);
}
private void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
*
*
* @param vUserClass
* the users class with all properties set
* @param coolDownDelay
*/
private void startVUser(final Class<?> vUserClass, final long coolDownDelay) {
LpeSystemUtils.submitTask(new Runnable() {
public void run() {
ISimpleVUser vUser;
try {
vUser = (ISimpleVUser) vUserClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
increaseNumActiveUsers();
while (!experimentPhaseFinished) {
vUser.executeIteration();
}
long coolDownPhaseStart = System.currentTimeMillis();
while ((System.currentTimeMillis() - coolDownPhaseStart) < coolDownDelay) {
vUser.executeIteration();
}
decreaseNumActiveUsers();
}
});
}
private Class<?> loadVUserScript(File userScriptFile, String userScriptClassNAme) throws WorkloadException {
URL url;
final Class<?> vUserClass;
try {
url = userScriptFile.toURI().toURL();
URL[] urls = new URL[] { url };
urlClassLoader = new URLClassLoader(urls, this.getClass().getClassLoader());
vUserClass = urlClassLoader.loadClass(userScriptClassNAme);
} catch (MalformedURLException e) {
throw new WorkloadException(e);
} catch (ClassNotFoundException e) {
// Cannot include ClassNotFoundException because it is not parsable
// as JSON string due to unrecognized field ("ex" instead of
// "exception")
throw new WorkloadException("Could not find specified class '" + e.getMessage() + "'");
}
return vUserClass;
}
/**
* Checks the passed properties, to contain values for the following keys:
* <ol>
* <li>{@link #USER_SCRIPT_PATH}</li>
* <li>{@link #USER_SCRIPT_CLASS_NAME}</li>
* <li>{@link #NUMBER_CURRENT_USERS}</li>
* </ol>
*
* @param properties
* the properties to check
*/
private void checkProperties(Properties properties) {
if (!properties.containsKey(USER_SCRIPT_PATH)) {
throw new RuntimeException("User script file has not been specified!");
}
if (!properties.containsKey(USER_SCRIPT_CLASS_NAME)) {
throw new RuntimeException("Class name for user script has not been specified");
}
}
/**
* Synchronized method to incerease the number of the users in the system up
* by one (+1).
*/
private synchronized void increaseNumActiveUsers() {
numActiveUsers++;
notifyAll();
}
/**
* Synchronized method to decrease the number of the users in the system by
* one (-1).
*/
private synchronized void decreaseNumActiveUsers() {
numActiveUsers--;
notifyAll();
}
@Override
public synchronized void waitForFinishedLoad() throws WorkloadException {
LOGGER.debug("waiting for finished load ...");
synchronized (this) {
while (numActiveUsers > 0 || !warmupPhaseFinished || !experimentPhaseFinished) {
LOGGER.debug("activeUsers: {}", numActiveUsers);
LOGGER.debug("warmupPhaseFinished: {}", warmupPhaseFinished);
LOGGER.debug("experimentPhaseFinished: {}", experimentPhaseFinished);
try {
this.wait();
} catch (InterruptedException e) {
throw new WorkloadException(e);
}
}
}
LOGGER.debug("load finished");
try {
workloadGenerationJob.get();
} catch (InterruptedException | ExecutionException e) {
throw new WorkloadException(e);
}
}
@Override
public void initialize() throws WorkloadException {
// TODO Auto-generated method stub
}
@Override
public void waitForWarmupPhaseTermination() throws WorkloadException {
LOGGER.debug("waiting for warm up phase termination ...");
synchronized (warmUpMonitor) {
while (!warmupPhaseFinished) {
try {
warmUpMonitor.wait();
} catch (InterruptedException e) {
throw new WorkloadException(e);
}
}
}
LOGGER.debug("warm-up phase terminated");
}
@Override
public void waitForExperimentPhaseTermination() throws WorkloadException {
LOGGER.debug("waiting for experiment phase termination ...");
synchronized (experimentMonitor) {
while (!experimentPhaseFinished) {
try {
LOGGER.debug("going to wait in: experiment phase termination ...");
experimentMonitor.wait();
} catch (InterruptedException e) {
throw new WorkloadException(e);
}
}
}
LOGGER.debug("experiment phase terminated");
}
@Override
protected void finalize() throws Throwable {
urlClassLoader.close();
}
}