All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.engineblock.core.ScenarioController Maven / Gradle / Ivy

Go to download

Runtime artifact for engineblock; This module ties the core libraries, provided drivers, and API into a single executable jar

There is a newer version: 2.12.65
Show newest version
/*
 *   Copyright 2015 jshook
 *   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 io.engineblock.core;

import io.engineblock.activityapi.core.Activity;
import io.engineblock.activityapi.core.ActivityType;
import io.engineblock.activityapi.core.ProgressMeter;
import io.engineblock.activityimpl.ActivityDef;
import io.engineblock.activityimpl.ParameterMap;
import io.engineblock.metrics.ActivityMetrics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.InvalidParameterException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * A ScenarioController provides a way to start Activities, modify them while running, and forceStopMotors, pause or restart them.
 */
public class ScenarioController {

    private static final Logger logger = LoggerFactory.getLogger(ScenarioController.class);

    private final Map activityExecutors = new ConcurrentHashMap<>();

    /**
     * Start an activity, given the activity definition for it. The activity will be known in the scenario
     * by the alias parameter.
     *
     * @param activityDef string in alias=value1;type=value2;... format
     */
    public synchronized void start(ActivityDef activityDef) {
        getActivityExecutor(activityDef, true).startActivity();
    }

    /**
     * Start an activity, given a map which holds the activity definition for it. The activity will be known in
     * the scenario by the alias parameter.
     *
     * @param activityDefMap A map containing the activity definition
     */
    public synchronized void start(Map activityDefMap) {
        ActivityDef ad = new ActivityDef(new ParameterMap(activityDefMap));
        start(ad);
    }

    /**
     * Start an activity, given the name by which it is known already in the scenario. This is useful if you have
     * stopped an activity and want to start it again.
     *
     * @param alias the alias of an activity that is already known to the scenario
     */
    public synchronized void start(String alias) {
        start(ActivityDef.parseActivityDef(alias));
    }

    public synchronized void run(int timeout, Map activityDefMap) {
        ActivityDef ad = new ActivityDef(new ParameterMap(activityDefMap));
        run(timeout, ad);
    }

    /**
     * Synchronously run the defined activity with a timeout in seconds.
     *
     * @param timeout     seconds to await completion of the activity.
     * @param activityDef A definition for an activity to run
     */
    public synchronized void run(int timeout, ActivityDef activityDef) {
        ActivityExecutor activityExecutor = getActivityExecutor(activityDef, true);
        activityExecutor.startActivity();
        activityExecutor.awaitCompletion(timeout);
    }

    public synchronized void run(int timeout, String activityDefString) {
        ActivityDef activityDef = ActivityDef.parseActivityDef(activityDefString);
        run(timeout, activityDef);
    }

    public synchronized void run(Map activityDefMap) {
        run(Integer.MAX_VALUE, activityDefMap);
    }

    public synchronized void run(String activityDefString) {
        run(Integer.MAX_VALUE, activityDefString);
    }

    public synchronized void run(ActivityDef activityDef) {
        run(Integer.MAX_VALUE, activityDef);
    }


    public boolean isRunningActivity(String alias) {
        return isRunningActivity(aliasToDef(alias));
    }

    public boolean isRunningActivity(ActivityDef activityDef) {

        ActivityExecutor activityExecutor = getActivityExecutor(activityDef, false);
        return activityExecutor != null && activityExecutor.isRunning();
    }

    public boolean isRunningActivity(Map activityDefMap) {
        ActivityDef ad = new ActivityDef(new ParameterMap(activityDefMap));
        return isRunningActivity(ad);
    }

    /**
     * 

Stop an activity, given an activity def. The only part of the activity def that is important is the * alias parameter. This method retains the activity def signature to provide convenience for scripting.

*

For example, sc.stop("alias=foo")

* * @param activityDef An activity def, including at least the alias parameter. */ public synchronized void stop(ActivityDef activityDef) { ActivityExecutor activityExecutor = getActivityExecutor(activityDef, false); if (activityExecutor == null) { throw new RuntimeException("could not stop missing activity:" + activityDef); } activityExecutor.stopActivity(); } /** *

Stop an activity, given an activity def map. The only part of the map that is important is the * alias parameter. This method retains the map signature to provide convenience for scripting.

* * @param activityDefMap A map, containing at least the alias parameter */ public synchronized void stop(Map activityDefMap) { ActivityDef ad = new ActivityDef(new ParameterMap(activityDefMap)); stop(ad); } /** * Stop an activity, given the name by which it is known already in the scenario. This causes the * activity to stop all threads, but keeps the thread objects handy for starting again. This can be useful * for certain testing scenarios in which you want to stop some workloads and start others based on other conditions. * * @param alias The name of the activity that is already known to the scenario */ public synchronized void stop(String alias) { stop(aliasToDef(alias)); } /** * Modify one of the parameters in a defined activity. Any observing activity components will be notified of the * changes made to activity parameters. * * @param alias The name of an activity that is already known to the scenario. * @param param The parameter name * @param value a new parameter value */ public synchronized void modify(String alias, String param, String value) { if (param.equals("alias")) { throw new InvalidParameterException("It is not allowed to change the name of an existing activity."); } ActivityExecutor activityExecutor = getActivityExecutor(alias); ParameterMap params = activityExecutor.getActivityDef().getParams(); params.set(param, value); } /** * Apply any parameter changes to a defined activity, or start a new one. * This method is syntactical sugar for scripting. Each of the parameters in the map * is checked against existing values, and per-field modifications * are applied one at a time, only if the values have changed. * * @param appliedParams Map of new values. */ public synchronized void apply(Map appliedParams) { String alias = appliedParams.get("alias"); if (alias == null) { throw new UserException("alias must be provided"); } ActivityExecutor executor = activityExecutors.get(alias); if (executor == null) { logger.info("started scenario from apply:" + alias); start(appliedParams); return; } ParameterMap previousMap = executor.getActivityDef().getParams(); for (String paramName : appliedParams.keySet()) { String appliedVal = appliedParams.get(paramName); Optional prevVal = previousMap.getOptionalString(paramName); if (!prevVal.isPresent() || !prevVal.get().equals(appliedVal)) { logger.info("applying new value to activity '" + alias + "': '" + prevVal.get() + "' -> '" + appliedVal + "'"); previousMap.set(paramName, appliedVal); } } } /** * Get the activity executor associated with the given alias. This should be used to find activitytypes * which are presumed to be already defined. * * @param activityAlias The activity alias for the extant activity. * @return the associated ActivityExecutor * @throws RuntimeException a runtime exception if the named activity is not found */ private ActivityExecutor getActivityExecutor(String activityAlias) { Optional executor = Optional.ofNullable(activityExecutors.get(activityAlias)); return executor.orElseThrow( () -> new RuntimeException("ActivityExecutor for alias " + activityAlias + " not found.") ); } private ActivityExecutor getActivityExecutor(ActivityDef activityDef, boolean createIfMissing) { synchronized (activityExecutors) { ActivityExecutor executor = activityExecutors.get(activityDef.getAlias()); if (executor == null && createIfMissing) { String activityTypeName = activityDef.getParams().getOptionalString("type").orElse(null); List knownTypes = ActivityType.FINDER.getAll().stream().map(ActivityType::getName).collect(Collectors.toList()); // Infer the type from either alias or yaml if possible (exactly one matches) if (activityTypeName==null) { List matching = knownTypes.stream().filter( n -> activityDef.getParams().getOptionalString("alias").orElse("").contains(n) || activityDef.getParams().getOptionalString("yaml").orElse("").contains(n) ).collect(Collectors.toList()); if (matching.size()==1) { activityTypeName=matching.get(0); logger.info("param 'type' was inferred as '" + activityTypeName + "' since it was seen in yaml or alias parameter."); } } if (activityTypeName==null) { String errmsg = "You must provide a type= parameter. Valid examples are:\n" + knownTypes.stream().map(t -> " type="+t+"\n").collect(Collectors.joining()); throw new UserException(errmsg); } ActivityType activityType = ActivityType.FINDER.getOrThrow(activityTypeName); executor = new ActivityExecutor(activityType.getAssembledActivity(activityDef, getActivityMap())); activityExecutors.put(activityDef.getAlias(), executor); } return executor; } } /** * Wait for a bit. This is not the best approach, and will be replace with a different system in the future. * * @param waitMillis time to wait, in milliseconds */ public void waitMillis(long waitMillis) { logger.trace("#> waitMillis(" + waitMillis + ")"); long endTime = System.currentTimeMillis() + waitMillis; while (waitMillis > 0L) { try { Thread.sleep(waitMillis); } catch (InterruptedException spurrious) { waitMillis = endTime - System.currentTimeMillis(); continue; } waitMillis = 0; } } /** * Return all the names of the activites that are known to this scenario. * * @return set of activity names */ public Set getAliases() { return activityExecutors.keySet(); } /** * Return all the activity definitions that are known to this scenario. * * @return list of activity defs */ public List getActivityDefs() { return activityExecutors.values().stream() .map(ActivityExecutor::getActivityDef) .collect(Collectors.toList()); } /** * Get the named activity def, if it is known to this scenario. * * @param alias The name by which the activity is known to this scenario. * @return an ActivityDef instance * @throws RuntimeException if the alias is not known to the scenario */ public ActivityDef getActivityDef(String alias) { return getActivityExecutor(alias).getActivityDef(); } /** * Force the scenario to stop running. Stop all activity threads, and after waitTimeMillis, force stop * all activity threads. An activity will stop its threads cooperatively in this time as long as the * internal cycles complete before the timer expires. * * @param waitTimeMillis grace period during which an activity may cooperatively shut down */ public void forceStopScenario(int waitTimeMillis) { logger.warn("Scenario force stopped."); activityExecutors.values().forEach(a -> a.forceStopExecutor(waitTimeMillis)); } /** * Await completion of all running activities, but do not force shutdownActivity. This method is meant to provide * the blocking point for calling logic. It waits. * * @param waitTimeMillis The time to wait, usually set very high * @return true, if all activities completed before the timer expired, false otherwise */ public boolean awaitCompletion(int waitTimeMillis) { boolean completed = false; for (ActivityExecutor executor : activityExecutors.values()) { if (!executor.awaitCompletion(waitTimeMillis)) return false; } return true; } private ActivityDef aliasToDef(String alias) { if (alias.contains("=")) { return ActivityDef.parseActivityDef(alias); } else { return ActivityDef.parseActivityDef("alias=" + alias + ";"); } } public boolean awaitActivity(Map activityDefMap) { ActivityDef ad = new ActivityDef(new ParameterMap(activityDefMap)); return awaitActivity(ad); } public boolean awaitActivity(String alias) { ActivityDef toAwait = aliasToDef(alias); return awaitActivity(toAwait); } public boolean awaitActivity(ActivityDef activityDef) { ActivityExecutor activityExecutor = getActivityExecutor(activityDef, false); if (activityExecutor == null) { throw new RuntimeException("Could not await missing activity: " + activityDef); } return activityExecutor.awaitFinish(Integer.MAX_VALUE); } /** * @return an unmodifyable String to executor map of all activities known to this scenario */ public Map getActivityExecutorMap() { return Collections.unmodifiableMap(activityExecutors); } public void reportMetrics() { ActivityMetrics.reportTo(System.out); } private Map getActivityMap() { Map activityMap = new HashMap(); for (Map.Entry entry : activityExecutors.entrySet()) { activityMap.put(entry.getKey(), entry.getValue().getActivity()); } return activityMap; } public Collection getProgressMeters() { return this.activityExecutors.values().stream().map(e -> (ProgressMeter) e).collect(Collectors.toList()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy