package cz.cuni.mff.d3s.been.cluster.context;
import static cz.cuni.mff.d3s.been.cluster.Names.BENCHMARKS_CONTEXT_ID;
import java.util.Collection;
import java.util.Date;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hazelcast.core.IMap;
import com.hazelcast.query.SqlPredicate;
import cz.cuni.mff.d3s.been.cluster.Names;
import cz.cuni.mff.d3s.been.core.benchmark.BenchmarkEntry;
import cz.cuni.mff.d3s.been.core.benchmark.ResubmitHistory;
import cz.cuni.mff.d3s.been.core.benchmark.ResubmitHistoryItem;
import cz.cuni.mff.d3s.been.core.benchmark.Storage;
import cz.cuni.mff.d3s.been.core.task.*;
/**
*
* Utility class for manipulating {@link BenchmarkEntry}.
*
* WARNING: Part of BEEN private API. The interface/semantics can change, you
* better know what you are doing!
*
*
* @author Martin Sixta
*/
public class Benchmarks {
/** slf4j logger */
private static final Logger log = LoggerFactory.getLogger(Benchmarks.class);
/** Connection to the cluster */
private final ClusterContext clusterContext;
/** Tasks context */
private final TaskContexts taskContexts;
/**
* Package private constructor.
*
* @param clusterContext
* the BEEN cluster context
*/
Benchmarks(ClusterContext clusterContext) {
this.clusterContext = clusterContext;
this.taskContexts = clusterContext.getTaskContexts();
}
/**
* Returns the map which holds all benchmark entries.
*
* @return {@link IMap} with benchmark entries.
*/
public IMap<String, BenchmarkEntry> getBenchmarksMap() {
return clusterContext.getMap(Names.BENCHMARKS_MAP_NAME);
}
/**
* Puts a benchmark entry to the benchmark map.
*
* The key to the map is
* {@link cz.cuni.mff.d3s.been.core.benchmark.BenchmarkEntry#getId()} of the
* entry.
*
* The call has semantics of (distributed)
* {@link java.util.concurrent.ConcurrentMap#put(Object, Object)}.
*
*
* WARNING: The benchmark entry is a shared resource and can be potentially
* modified concurrently, use with care.
*
* @see IMap#put(Object, Object)
* @see IMap#lock(Object)
*
* @param entry
* the entry to put/update
* @return old value of the entry
*/
public BenchmarkEntry put(BenchmarkEntry entry) {
return getBenchmarksMap().put(entry.getId(), entry);
}
/**
* Returns a benchmark entry with the id.
*
* The call has semantics of (distributed)
* {@link java.util.concurrent.ConcurrentMap#get(Object)}.
*
* @param id
* ID of the benchmark entry to get
*
* @return the entry with the id or null
*
* @see IMap#get(Object)
*/
public BenchmarkEntry get(String id) {
return getBenchmarksMap().get(id);
}
/**
* Returns all currently known task contexts of a given benchmark.
*
* WARNING: does not return all contexts ever generated, but only the current
* ones
*
* @param benchmarkId
* ID of a benchmark to get contexts for
* @return all currently known task contexts of a given benchmark
*/
public Collection<TaskContextEntry> getTaskContextsInBenchmark(String benchmarkId) {
String query = String.format("benchmarkId = '%s'", benchmarkId);
return taskContexts.getTaskContextsMap().values(new SqlPredicate(query));
}
/**
* Atomically adds a (generator) task to the special benchmark task context.
*
* The benchmark task context keeps track of all generator tasks.
*
*
* @param taskEntry
* (generator) task to add
*/
public void addBenchmarkToBenchmarksContext(TaskEntry taskEntry) {
IMap<String, TaskContextEntry> taskContextsMap = taskContexts.getTaskContextsMap();
try {
taskContextsMap.lock(BENCHMARKS_CONTEXT_ID);
TaskContextEntry taskContextEntry = taskContextsMap.get(BENCHMARKS_CONTEXT_ID);
if (taskContextEntry == null) {
taskContextEntry = new TaskContextEntry();
taskContextEntry.setId(BENCHMARKS_CONTEXT_ID);
taskContextEntry.setContextState(TaskContextState.RUNNING);
taskContextEntry.setLingering(true); // do not destroy this context
taskContextEntry.setCreated(new Date().getTime());
}
taskContextEntry.getContainedTask().add(taskEntry.getId());
taskContextsMap.put(BENCHMARKS_CONTEXT_ID, taskContextEntry);
} finally {
taskContextsMap.unlock(BENCHMARKS_CONTEXT_ID);
}
}
/**
* Atomically removes a (generator) task from the special benchmark task
* context.
*
* The benchmark task context keeps track of all generator tasks.
*
*
* Currently there is no automatic clean-up (on purpose) of generator tasks,
* we want to have a user well informed.
*
* @param taskEntry
* (generator) task to remove
*/
public void removeBenchmarkFromBenchmarksContext(TaskEntry taskEntry) {
IMap<String, TaskContextEntry> taskContextsMap = taskContexts.getTaskContextsMap();
taskContextsMap.lock(BENCHMARKS_CONTEXT_ID);
try {
TaskContextEntry taskContextEntry = taskContextsMap.get(BENCHMARKS_CONTEXT_ID);
if (taskContextEntry == null) {
// TODO
throw new RuntimeException("Benchmarks context does not exist.");
}
taskContextEntry.getContainedTask().remove(taskEntry.getId());
taskContextsMap.put(BENCHMARKS_CONTEXT_ID, taskContextEntry);
} finally {
taskContextsMap.unlock(BENCHMARKS_CONTEXT_ID);
}
}
/**
* Submits a generator task.
*
* Task schedulability rules apply.
*
* Does not lock (no need since the task does not exist yet)
*
* @param benchmarkTaskDescriptor
* description of the generator task
* @return ID of the submitted task
* @throws IllegalArgumentException
* when trying to submit task which is not of type
* {@link TaskType#BENCHMARK`}
*/
public String submit(TaskDescriptor benchmarkTaskDescriptor) throws IllegalArgumentException {
if (benchmarkTaskDescriptor.getType() != TaskType.BENCHMARK) {
throw new IllegalArgumentException("The task is not a benchmark generator!");
}
BenchmarkEntry benchmarkEntry = new BenchmarkEntry();
benchmarkEntry.setStorage(new Storage());
benchmarkEntry.setAllowResubmit(true);
benchmarkEntry.setResubmitHistory(new ResubmitHistory());
String benchmarkId = UUID.randomUUID().toString();
benchmarkEntry.setId(benchmarkId);
String taskId = taskContexts.submitBenchmarkTask(benchmarkTaskDescriptor, benchmarkId);
benchmarkEntry.setGeneratorId(taskId);
put(benchmarkEntry);
return benchmarkEntry.getId();
}
/**
*
* Resubmits a benchmark's generator task.
*
* Task schedulability rules apply.
*
* WARNING: does not lock the entry
*
* WARNING: the function is meant to be called after a generator failure is
* detected. The detection logic is implemented in TM and must lock the entry
* first
*
* TODO: merge with the detection logic to TM? if there are more users (user
* request?) it should lock.
*
*
* @param benchmarkEntry
* entry to resubmit
* @return ID of the new generator task
*/
public String resubmit(BenchmarkEntry benchmarkEntry) {
String benchmarkId = benchmarkEntry.getId();
String oldGeneratorId = benchmarkEntry.getGeneratorId();
TaskEntry generatorEntry = clusterContext.getTasks().getTask(oldGeneratorId);
TaskDescriptor benchmarkTaskDescriptor = generatorEntry.getTaskDescriptor();
String oldRuntimeId = generatorEntry.getRuntimeId();
String newGeneratorId = taskContexts.submitBenchmarkTask(benchmarkTaskDescriptor, benchmarkId);
benchmarkEntry.setGeneratorId(newGeneratorId);
ResubmitHistoryItem i = new ResubmitHistoryItem();
i.setTimestamp(new Date().getTime());
i.setOldRuntimeId(oldRuntimeId);
i.setOldGeneratorId(oldGeneratorId);
benchmarkEntry.getResubmitHistory().getResubmitHistoryItem().add(i);
put(benchmarkEntry);
return benchmarkEntry.getId();
}
/**
* Removes the benchmark entry with the specified ID from Hazelcast map of
* benchmarks. The benchmark's generator must be in a final state (finished,
* aborted) or already removed. Also removes all existing task contexts that
* belong to this benchmark. Also removes all "old generators", which have
* failed and were resubmitted.
*
* @param benchmarkId
* ID of the benchmark to remove
*/
public void remove(String benchmarkId) {
BenchmarkEntry benchmarkEntry = get(benchmarkId);
String generatorId = benchmarkEntry.getGeneratorId();
TaskEntry generatorEntry = clusterContext.getTasks().getTask(generatorId);
if (generatorEntry == null || generatorEntry.getState() == TaskState.FINISHED || generatorEntry.getState() == TaskState.ABORTED) {
log.info("Removing benchmark entry {} from map.", benchmarkId);
// remove all existing contexts from the benchmark
for (TaskContextEntry taskContextEntry : getTaskContextsInBenchmark(benchmarkId)) {
clusterContext.getTaskContexts().remove(taskContextEntry.getId());
}
// remove failed generators
for (ResubmitHistoryItem resubmitHistoryItem : benchmarkEntry.getResubmitHistory().getResubmitHistoryItem()) {
String oldGeneratorId = resubmitHistoryItem.getOldGeneratorId();
TaskEntry oldGeneratorEntry = clusterContext.getTasks().getTask(oldGeneratorId);
if (oldGeneratorEntry != null) {
clusterContext.getTasks().remove(oldGeneratorId);
removeBenchmarkFromBenchmarksContext(oldGeneratorEntry);
}
}
// remove current generator
clusterContext.getTasks().remove(generatorId);
// remove generator from the Been special context for generators
if (generatorEntry != null) {
removeBenchmarkFromBenchmarksContext(generatorEntry);
}
// remove the entry
getBenchmarksMap().remove(benchmarkId);
} else {
throw new IllegalStateException(String.format(
"Trying to remove benchmark entry %s, but it's generator is in state %s.",
benchmarkEntry,
generatorEntry.getState()));
}
}
/**
* Issues a "kill task" message for the benchmark generator. This message will
* be delivered to the appropriate host runtime, which will try to kill the
* generator of the benchmark. Running contexts and tasks of the benchmark are
* *not* affected.
*
* @param benchmarkId
* ID of the benchmark to kill
*/
public void kill(String benchmarkId) {
IMap<String, BenchmarkEntry> benchmarksMap = getBenchmarksMap();
try {
benchmarksMap.lock(benchmarkId);
BenchmarkEntry benchmarkEntry = get(benchmarkId);
String generatorId = benchmarkEntry.getGeneratorId();
TaskEntry generatorEntry = clusterContext.getTasks().getTask(generatorId);
if (generatorEntry == null) {
throw new IllegalStateException(String.format("The generator of benchmark %s does not exist.", benchmarkId));
}
if (generatorEntry.getState() == TaskState.FINISHED || generatorEntry.getState() == TaskState.ABORTED) {
throw new IllegalStateException(String.format(
"Trying to kill benchmark %s, but it's generator is in state %s.",
benchmarkId,
generatorEntry.getState()));
}
benchmarkEntry.setAllowResubmit(false);
put(benchmarkEntry);
clusterContext.getTasks().kill(generatorId);
} finally {
benchmarksMap.unlock(benchmarkId);
}
}
/**
* Disallow any further resubmits for the specified benchmark. This means that
* when the generator task fails, the whole benchmark will be terminated.
* Currently running generator is not affected.
*
* This method locks the benchmark entry before applying the setting.
*
* @param benchmarkId
* ID of the benchmark for which resubmits should be disallowed
*/
public void disallowResubmits(String benchmarkId) {
IMap<String, BenchmarkEntry> benchmarksMap = getBenchmarksMap();
benchmarksMap.lock(benchmarkId);
try {
BenchmarkEntry benchmarkEntry = get(benchmarkId);
benchmarkEntry.setAllowResubmit(false);
put(benchmarkEntry);
} finally {
benchmarksMap.unlock(benchmarkId);
}
}
}