Rule.java
package emissary.core.sentinel.protocols.rules;
import emissary.core.NamespaceException;
import emissary.core.sentinel.protocols.Protocol;
import emissary.pool.AgentPool;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public abstract class Rule {
protected static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
// the name of the rule
protected final String name;
// the place name to test the condition
protected final Pattern place;
// how long to wait before alerting on stuck agents
protected final long timeLimit;
// percentage of mobile agents that are stuck on the same place before sounding the alarm
protected final double threshold;
public Rule(String name, String place, long timeLimit, double threshold) {
logger.trace("Creating rule for name={}, place={}, timeLimit={}, threshold={}", name, place, timeLimit, threshold);
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("Invalid name [" + name + "]");
}
if (StringUtils.isBlank(place)) {
throw new IllegalArgumentException("Invalid place pattern [" + place + "]");
}
if (timeLimit <= 0) {
throw new IllegalArgumentException("Invalid timeLimit [" + timeLimit + "], must be greater than 0");
}
if (threshold <= 0.0 || threshold > 1.0) {
throw new IllegalArgumentException("Invalid threshold [" + threshold + "], expected a value > 0.0 or <= 1.0");
}
this.name = name;
this.place = Pattern.compile(place);
this.timeLimit = timeLimit;
this.threshold = threshold;
}
public Rule(String name, String place, String timeLimit, String threshold) {
this(name, place, StringUtils.isBlank(timeLimit) ? 60L : Long.parseLong(timeLimit),
StringUtils.isBlank(threshold) ? 1.0 : Double.parseDouble(threshold));
}
/**
* Check the rule conditions
*
* @param placeAgentStats collection of the stats of a place that is currently processing
* @return true if conditions are met, false otherwise
*/
public boolean condition(Collection<Protocol.PlaceAgentStats> placeAgentStats) {
List<Protocol.PlaceAgentStats> filtered =
placeAgentStats.stream().filter(p -> place.matcher(p.getPlace()).matches()).collect(Collectors.toList());
return overThreshold(filtered) && overTimeLimit(filtered);
}
/**
* Check to see if the number of places in mobile agents are over the configured threshold
*
* @param placeAgentStats the stats of a place that is currently processing
* @return true if the number of mobile agents stuck on the place is over the threshold, false otherwise
*/
protected boolean overThreshold(Collection<Protocol.PlaceAgentStats> placeAgentStats) {
int count = placeAgentStats.stream().mapToInt(Protocol.PlaceAgentStats::getCount).sum();
int poolSize = getAgentCount();
logger.debug("Testing threshold for place={}, counter={}, poolSize={}, threshold={}", place, count, poolSize, threshold);
return (double) count / poolSize >= this.threshold;
}
/**
* Get the total number of agents, idle and active. Override this method to
*
* @return the total number of agents
*/
protected int getAgentCount() {
try {
return AgentPool.lookup().getCurrentPoolSize();
} catch (NamespaceException ne) {
throw new IllegalStateException(ne);
}
}
/**
* Check to see if the places in mobile agents are over the configured time limit
*
* @param placeAgentStats the stats of a place that is currently processing
* @return true if the places in mobile agents are over the configured time limit, false otherwise
*/
protected abstract boolean overTimeLimit(Collection<Protocol.PlaceAgentStats> placeAgentStats);
@Override
public String toString() {
return new StringJoiner(", ", "{", "}")
.add("\"name\":\"" + name + "\"")
.add("\"rule\":\"" + getClass().getSimpleName() + "\"")
.add("\"place\":\"" + place + "\"")
.add("\"timeLimitInMinutes\":" + timeLimit)
.add("\"threshold\":" + threshold)
.toString();
}
}