/******************************************************************************* * Copyright (c) 2014 Imperial College London * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Raul Castro Fernandez - initial API and implementation ******************************************************************************/ package uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.trigger; import java.util.List; import java.util.Objects; import org.joda.time.Period; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.metric.MetricName; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.metric.MetricValue; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.threshold.MetricThreshold; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.threshold.TimeThreshold; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.util.MetricReading; import uk.ac.imperial.lsds.seep.infrastructure.monitor.policy.util.TimeReference; /** * Trigger that evaluates a metric value threshold and a time threshold. If both * thresholds evaluate to true, then the trigger state changes to fired. Otherwise, * the trigger reverts to state non-fired. * * @author mrouaux */ public class ActionTrigger { private static final Logger logger = LoggerFactory.getLogger(ActionTrigger.class); public enum ActionTriggerState { FIRED, NON_FIRED } private MetricThreshold valueThreshold; private TimeThreshold timeThreshold; private MetricName metricName; private ActionTriggerState triggerState; private boolean stateChanged; /** * Convenience constructor * @param valueThreshold Value threshold that will change the state of the trigger * @param timeThreshold Time threshold that will change the state of the trigger * @param metricName Name of the metric associated to this trigger. */ public ActionTrigger( final MetricThreshold valueThreshold, final TimeThreshold timeThreshold, final MetricName metricName) { this.valueThreshold = valueThreshold; this.timeThreshold = timeThreshold; this.metricName = metricName; this.triggerState = ActionTriggerState.NON_FIRED; this.stateChanged = false; } /** * Evaluates the state of the trigger. Both the value and the time threshold * need to evaluate to true in order for the trigger state to change from * non-fired to fired. * @param readings metric readings to evaluate (all those that are within the * time threshold need to evaluate to true in terms of their value). */ public void evaluate(List<MetricReading> readings, TimeReference time) { logger.info("Evaluating trigger for " + metricName.toString() + " - " + readings.size() + " readings provided"); logger.debug("value threshold: " + valueThreshold.toString()); logger.debug("time threshold: " + timeThreshold.toString()); // Determine the new state of the trigger depending on the result of // evaluating boh thresholds. Need to mark flag if state changes. ActionTriggerState pastTriggerState = triggerState; int i = 0; for(MetricReading r : readings) { Period metricPeriod = new Period(r.getTimestamp(), time.now()); MetricValue metricValue = r.getValues().get(metricName); logger.info("Evaluating reading[" + i + "] value[" + metricValue.toString() + "] period[" + metricPeriod.toString() + "]"); // We evaluate the time threshold first, simple optimisation to be // able to abort the iteration sooner (readings are guaranteed to be // sorted by time of reception, from most recent to least recenet). if(timeThreshold.evaluate(metricPeriod)) { triggerState = valueThreshold.evaluate(metricValue)? ActionTriggerState.FIRED: ActionTriggerState.NON_FIRED; // If there is a reading within the time threshold for which // the value evaluates to false (trigger is non-fired), then // we can break from the evaluation loop. if(triggerState.equals(ActionTriggerState.NON_FIRED)) { break; } } i++; } stateChanged = (triggerState != pastTriggerState); logger.info("New trigger state is [" + triggerState.toString() + "] changed[" + stateChanged + "]"); } /** * @return True if the state of the trigger is fired. False otherwise. */ public boolean isFired() { return (triggerState.equals(ActionTriggerState.FIRED)); } /** * @return True if the state of the trigger changed during the last evaluation. */ public boolean hasChanged() { return stateChanged; } /** * @return Value threshold for the trigger */ public MetricThreshold getValueThreshold() { return valueThreshold; } /** * @return Time threshold for the trigger */ public TimeThreshold getTimeThreshold() { return timeThreshold; } /** * @return Metric name for the trigger */ public MetricName getMetricName() { return metricName; } @Override public int hashCode() { int hash = 7; hash = 47 * hash + Objects.hashCode(this.valueThreshold); hash = 47 * hash + Objects.hashCode(this.timeThreshold); hash = 47 * hash + (this.metricName != null ? this.metricName.hashCode() : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final ActionTrigger other = (ActionTrigger) obj; if (!Objects.equals(this.valueThreshold, other.valueThreshold)) { return false; } if (!Objects.equals(this.timeThreshold, other.timeThreshold)) { return false; } if (this.metricName != other.metricName) { return false; } return true; } @Override public String toString() { return "ActionTrigger{" + "valueThreshold=" + valueThreshold + ", timeThreshold=" + timeThreshold + ", metricName=" + metricName + '}'; } }