/*
* Copyright (c) 2010-2012 Grid Dynamics Consulting Services, Inc, All Rights Reserved
* http://www.griddynamics.com
*
* This library is free software; you can redistribute it and/or modify it under the terms of
* the Apache License; either
* version 2.0 of the License, or any later version.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.griddynamics.jagger.engine.e1.scenario;
import com.griddynamics.jagger.coordinator.Command;
import com.griddynamics.jagger.coordinator.Coordination;
import com.griddynamics.jagger.coordinator.NodeId;
import com.griddynamics.jagger.coordinator.RemoteExecutor;
import com.griddynamics.jagger.engine.e1.process.AddUrlClassLoader;
import com.griddynamics.jagger.engine.e1.process.ChangeWorkloadConfiguration;
import com.griddynamics.jagger.engine.e1.process.PollWorkloadProcessStatus;
import com.griddynamics.jagger.engine.e1.process.RemoveUrlClassLoader;
import com.griddynamics.jagger.engine.e1.process.ScenarioContext;
import com.griddynamics.jagger.engine.e1.process.StartWorkloadProcess;
import com.griddynamics.jagger.engine.e1.process.StopWorkloadProcess;
import com.griddynamics.jagger.engine.e1.process.WorkloadStatus;
import com.griddynamics.jagger.invoker.KernelInfo;
import com.griddynamics.jagger.util.TimeoutsConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.Set;
public class DefaultWorkloadController implements WorkloadController {
private static final Logger log = LoggerFactory.getLogger(DefaultWorkloadController.class);
private final String sessionId;
private final String taskId;
private final Map<NodeId, RemoteExecutor> remotes;
private final Long startTime;
private final TimeoutsConfiguration timeoutsConfiguration;
private final WorkloadTask task;
private Progress progress;
private Map<NodeId, String> processes;
private Map<NodeId, Integer> threads;
private Map<NodeId, Integer> delays;
private Map<NodeId, Integer> poolSize;
private Map<NodeId, Integer> nodeToIndex;
private String classesUrl;
public void setClassesUrl(String classesUrl) {
this.classesUrl = classesUrl;
}
public DefaultWorkloadController(final String sessionId,
final String taskId,
final WorkloadTask task,
final Map<NodeId, RemoteExecutor> remotes,
final TimeoutsConfiguration timeoutsConfiguration,
final Long startTime) {
this.sessionId = Preconditions.checkNotNull(sessionId);
this.taskId = Preconditions.checkNotNull(taskId);
this.task = Preconditions.checkNotNull(task);
this.remotes = ImmutableMap.copyOf(remotes);
this.timeoutsConfiguration = timeoutsConfiguration;
this.startTime = startTime;
progress = Progress.IDLE;
processes = Maps.newHashMap();
threads = Maps.newHashMap();
delays = Maps.newHashMap();
nodeToIndex = Maps.newHashMap();
int index = 0;
for (NodeId nodeId : remotes.keySet()) {
nodeToIndex.put(nodeId, index++);
}
nodeToIndex = ImmutableMap.copyOf(nodeToIndex);
}
@Override
public Set<NodeId> getNodes() {
return remotes.keySet();
}
@Override
public WorkloadExecutionStatus getStatus() {
Preconditions.checkState(progress == Progress.STARTED, "Workload should be started to get status");
log.debug("Workload status requested");
WorkloadExecutionStatusBuilder builder = new WorkloadExecutionStatusBuilder(task);
for (Map.Entry<NodeId, RemoteExecutor> entry : remotes.entrySet()) {
Long pollTime = System.currentTimeMillis();
Long durationTime = System.currentTimeMillis()-startTime;
NodeId id = entry.getKey();
RemoteExecutor remote = entry.getValue();
WorkloadStatus status;
String processId = processes.get(id);
if (processId != null) {
status = remote.runSyncWithTimeout(PollWorkloadProcessStatus.create(sessionId, processId),
Coordination.<Command<WorkloadStatus>>doNothing(),
timeoutsConfiguration.getWorkloadPollingTimeout());
} else {
status = new WorkloadStatus(0, 0, 0, 0);
}
Integer threadsOnNode = threads.get(id);
Integer delay = delays.get(id);
log.debug("{} Polled status: node {}, threads on node {}, samples started {}, samples finished {}, empty transactions {} with delay {}",
pollTime,
id,
threadsOnNode,
status.getStartedSamples(),
status.getFinishedSamples(),
status.getEmptyTransactions(),
delay);
builder.addNodeInfo(id,
status.getCurrentThreadNumber(),
status.getStartedSamples(),
status.getFinishedSamples(),
status.getEmptyTransactions(),
delay,
pollTime,
durationTime);
}
return builder.build();
}
@Override
public void startWorkload(Map<NodeId, Integer> poolSize) {
Preconditions.checkState(progress == Progress.IDLE, "Workload should be idle to get started");
log.debug("Workload start requested");
for (NodeId nodeId : remotes.keySet()) {
threads.put(nodeId, 0);
delays.put(nodeId, 0);
if (classesUrl != null) {
RemoteExecutor executor = remotes.get(nodeId);
AddUrlClassLoader addUrlClassLoaderCommand = AddUrlClassLoader.create(sessionId, classesUrl);
log.info("Sending command to add a class loader with classes url {} to node {}", classesUrl, nodeId);
executor.runSyncWithTimeout(addUrlClassLoaderCommand,
Coordination.<Command>doNothing(),
timeoutsConfiguration.getWorkloadStartTimeout());
log.info("Class loader with classes url {} has been added to node {}", classesUrl, nodeId);
}
}
this.poolSize = poolSize;
progress = Progress.STARTED;
}
@Override
public void adjustConfiguration(NodeId id, WorkloadConfiguration newConfiguration) {
Preconditions.checkState(progress == Progress.STARTED, "Workload should be started to get adjust task number");
log.debug("Adjusting task number with threads {} on node {} is requested", threads, id);
boolean workloadStarted = processes.containsKey(id);
if (workloadStarted) {
log.debug("Process is started. Going to change workload configuration");
changeWorkload(id, newConfiguration);
} else {
log.debug("Process is not started. Going to start workload");
startWorkload(id, newConfiguration);
}
}
@Override
public void stopWorkload() {
Preconditions.checkState(progress == Progress.STARTED, "Workload should be started to stop processes");
log.debug("Workload stop requested");
for (Map.Entry<NodeId, String> entry : processes.entrySet()) {
NodeId id = entry.getKey();
String processId = entry.getValue();
RemoteExecutor executor = remotes.get(id);
StopWorkloadProcess stop = StopWorkloadProcess.create(sessionId, processId);
log.debug("Going to stop process {} on node {}", processId, id);
executor.runSyncWithTimeout(stop, Coordination.<Command>doNothing(), timeoutsConfiguration.getWorkloadStopTimeout());
log.debug("Process {} is stopped on node {}", processId, id);
if (classesUrl != null) {
RemoveUrlClassLoader removeUrlClassLoaderCommand = RemoveUrlClassLoader.create(sessionId);
log.info("Sending command to remove a custom class loader to node {}", id);
executor.runSyncWithTimeout(removeUrlClassLoaderCommand,
Coordination.<Command>doNothing(),
timeoutsConfiguration.getWorkloadStopTimeout());
log.info("A custom class loader has been removed from node {}", id);
}
}
log.debug("Workload stopped");
progress = Progress.STOPPED;
}
private void changeWorkload(NodeId node, WorkloadConfiguration newConfiguration) {
String processId = processes.get(node);
RemoteExecutor remote = remotes.get(node);
remote.runSyncWithTimeout(ChangeWorkloadConfiguration.create(sessionId, processId, newConfiguration), Coordination.<Command>doNothing(), timeoutsConfiguration.getWorkloadPollingTimeout());
threads.put(node, newConfiguration.getThreads());
delays.put(node, newConfiguration.getDelay());
}
private void startWorkload(NodeId node, WorkloadConfiguration configuration) {
ScenarioContext scenarioContext = new ScenarioContext(taskId, task.getName(), task.getVersion(), configuration);
Integer nodePoolSize = poolSize.get(node);
if (nodePoolSize == null) {
nodePoolSize = 1;
}
StartWorkloadProcess start = StartWorkloadProcess.create(sessionId, scenarioContext, nodePoolSize);
start.setScenarioFactory(task.getScenarioFactory());
start.setKernelInfo(new KernelInfo(nodeToIndex.get(node), nodeToIndex.size()));
start.setCollectors(task.getCollectors());
start.setValidators(task.getValidators());
start.setListeners(task.getListeners());
log.debug("Going to start process {} on node {}", start, node);
RemoteExecutor remote = remotes.get(node);
String processId = remote.runSyncWithTimeout(start, Coordination.<StartWorkloadProcess>doNothing(), timeoutsConfiguration.getWorkloadStartTimeout());
log.debug("Process with id {} is started on node {}", processId, node);
processes.put(node, processId);
threads.put(node, configuration.getThreads());
delays.put(node, configuration.getDelay());
}
private static enum Progress {
IDLE, STARTED, STOPPED
}
}