Adding subalarm values to alarm state transitions

Change-Id: Ifeb8fa8e203adfab8ec84ec3aad13835dc2c62ea
This commit is contained in:
Michael James Hoppal 2015-02-19 16:09:09 -07:00
parent 6e4fb0720a
commit 35d3976342
6 changed files with 101 additions and 45 deletions

View File

@ -20,6 +20,7 @@ package monasca.thresh.domain.model;
import monasca.common.model.alarm.AlarmExpression;
import monasca.common.model.alarm.AlarmState;
import monasca.common.model.alarm.AlarmSubExpression;
import monasca.common.model.alarm.AlarmTransitionSubAlarm;
import monasca.common.model.domain.common.AbstractEntity;
import java.util.ArrayList;
@ -43,7 +44,7 @@ public class Alarm extends AbstractEntity {
private AlarmState state;
private String stateChangeReason;
private String alarmDefinitionId;
private List<AlarmTransitionSubAlarm> transitionSubAlarms = new ArrayList<>();
public Alarm() {
}
@ -59,13 +60,24 @@ public class Alarm extends AbstractEntity {
this.alarmDefinitionId = alarmDefinition.getId();
}
static String buildStateChangeReason(AlarmState alarmState, List<String> subAlarmExpressions) {
public String buildStateChangeReason(AlarmState alarmState) {
StringBuilder stringBuilder = new StringBuilder();
for(AlarmTransitionSubAlarm alarmTransitionSubAlarm : transitionSubAlarms){
if (alarmTransitionSubAlarm.subAlarmState.equals(alarmState)) {
if (stringBuilder.length() != 0) {
stringBuilder.append(", ");
}
stringBuilder.append(alarmTransitionSubAlarm.subAlarmExpression);
if (!AlarmState.UNDETERMINED.equals(alarmState))
stringBuilder.append(" with the values: ").append(alarmTransitionSubAlarm.currentValues);
}
}
if (AlarmState.UNDETERMINED.equals(alarmState)) {
return String.format("No data was present for the sub-alarms: %s", subAlarmExpressions);
return String.format("No data was present for the sub-alarms: %s", stringBuilder.toString());
} else if (AlarmState.ALARM.equals(alarmState)) {
return String.format("Thresholds were exceeded for the sub-alarms: %s", subAlarmExpressions);
return String.format("Thresholds were exceeded for the sub-alarms: %s", stringBuilder.toString());
} else {
return "The alarm threshold(s) have not been exceeded";
return String.format("The alarm threshold(s) have not been exceeded for the sub-alarms: %s", stringBuilder.toString());
}
}
@ -116,21 +128,25 @@ public class Alarm extends AbstractEntity {
* alarm's state changed, else false.
*/
public boolean evaluate(AlarmExpression expression) {
transitionSubAlarms.clear();
AlarmState initialState = state;
List<String> unitializedSubAlarms = new ArrayList<String>();
boolean uninitialized = false;
for (SubAlarm subAlarm : subAlarms.values()) {
if (AlarmState.UNDETERMINED.equals(subAlarm.getState())) {
unitializedSubAlarms.add(subAlarm.getExpression().toString());
uninitialized = true;
}
transitionSubAlarms.add(new AlarmTransitionSubAlarm(subAlarm.getExpression(),
subAlarm.getState(), subAlarm.getCurrentValues()));
}
// Handle UNDETERMINED state
if (!unitializedSubAlarms.isEmpty()) {
if (uninitialized) {
if (AlarmState.UNDETERMINED.equals(initialState)) {
return false;
}
state = AlarmState.UNDETERMINED;
stateChangeReason = buildStateChangeReason(state, unitializedSubAlarms);
stateChangeReason = buildStateChangeReason(state);
return true;
}
@ -146,16 +162,8 @@ public class Alarm extends AbstractEntity {
if (AlarmState.ALARM.equals(initialState)) {
return false;
}
List<String> subAlarmExpressions = new ArrayList<String>();
for (SubAlarm subAlarm : subAlarms.values()) {
if (AlarmState.ALARM.equals(subAlarm.getState())) {
subAlarmExpressions.add(subAlarm.getExpression().toString());
}
}
state = AlarmState.ALARM;
stateChangeReason = buildStateChangeReason(state, subAlarmExpressions);
stateChangeReason = buildStateChangeReason(state);
return true;
}
@ -163,7 +171,7 @@ public class Alarm extends AbstractEntity {
return false;
}
state = AlarmState.OK;
stateChangeReason = buildStateChangeReason(state, null);
stateChangeReason = buildStateChangeReason(state);
return true;
}
@ -256,4 +264,12 @@ public class Alarm extends AbstractEntity {
public void addAlarmedMetric(MetricDefinitionAndTenantId alarmedMetric) {
this.alarmedMetrics.add(alarmedMetric);
}
public List<AlarmTransitionSubAlarm> getTransitionSubAlarms() {
return transitionSubAlarms;
}
public void setTransitionSubAlarms(List<AlarmTransitionSubAlarm> transitionSubAlarms) {
this.transitionSubAlarms = transitionSubAlarms;
}
}

View File

@ -22,6 +22,8 @@ import monasca.common.model.alarm.AlarmSubExpression;
import monasca.common.model.domain.common.AbstractEntity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Sub-alarm. Decorates an AlarmSubExpression.
@ -34,6 +36,7 @@ public class SubAlarm extends AbstractEntity implements Serializable {
private AlarmSubExpression expression;
private AlarmState state;
private boolean noState;
private List<Double> currentValues;
/**
* Whether metrics for this sub-alarm are received sporadically.
*/
@ -55,6 +58,7 @@ public class SubAlarm extends AbstractEntity implements Serializable {
this.expression = expression.getAlarmSubExpression();
this.alarmSubExpressionId = expression.getId();
this.state = state;
this.currentValues = new ArrayList<>();
}
@Override
@ -113,6 +117,22 @@ public class SubAlarm extends AbstractEntity implements Serializable {
return alarmSubExpressionId;
}
public List<Double> getCurrentValues() {
return currentValues;
}
public void setCurrentValues(List<Double> currentValues) {
this.currentValues = currentValues;
}
public void addCurrentValue(Double currentValue) {
this.currentValues.add(currentValue);
}
public void clearCurrentValues() {
this.currentValues.clear();
}
@Override
public int hashCode() {
final int prime = 31;
@ -146,8 +166,8 @@ public class SubAlarm extends AbstractEntity implements Serializable {
@Override
public String toString() {
return String.format("SubAlarm [id=%s, alarmId=%s, alarmSubExpressionId=%s, expression=%s, state=%s noState=%s]", id,
alarmId, alarmSubExpressionId, expression, state, noState);
return String.format("SubAlarm [id=%s, alarmId=%s, alarmSubExpressionId=%s, expression=%s, state=%s, noState=%s, currentValues:[", id,
alarmId, alarmSubExpressionId, expression, state, noState) + currentValues + "]]";
}
/**

View File

@ -122,10 +122,12 @@ public class SubAlarmStats {
double[] values = stats.getViewValues();
boolean thresholdExceeded = false;
boolean hasEmptyWindows = false;
subAlarm.clearCurrentValues();
for (double value : values) {
if (Double.isNaN(value)) {
hasEmptyWindows = true;
} else {
subAlarm.addCurrentValue(value);
emptyWindowObservations = 0;
// Check if value is OK
@ -173,8 +175,6 @@ public class SubAlarmStats {
/**
* If this.subAlarm.isCompatible(newExpression) is not true, all data
* will be flushed
*
* @param subAlarm
*/
public void updateSubAlarm(final AlarmSubExpression newExpression, long viewEndTimestamp) {
// Save the old state

View File

@ -228,8 +228,8 @@ public class AlarmThresholdingBolt extends BaseRichBolt {
new AlarmStateTransitionedEvent(alarmDefinition.getTenantId(), alarm.getId(),
alarmDefinition.getId(), alarmedMetrics, alarmDefinition.getName(),
alarmDefinition.getDescription(), initialState, alarm.getState(),
alarmDefinition.getSeverity(), alarmDefinition.isActionsEnabled(), stateChangeReason,
getTimestamp());
alarmDefinition.getSeverity(), alarmDefinition.isActionsEnabled(), stateChangeReason,
alarm.getTransitionSubAlarms(), getTimestamp());
try {
alarmEventForwarder.send(Serialization.toJson(event));
} catch (Exception ignore) {
@ -257,10 +257,6 @@ public class AlarmThresholdingBolt extends BaseRichBolt {
}
String buildStateChangeReason() {
return null;
}
private Alarm getOrCreateAlarm(String alarmId) {
Alarm alarm = alarms.get(alarmId);
if (alarm == null) {

View File

@ -20,11 +20,14 @@ package monasca.thresh.domain.model;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import monasca.common.model.alarm.AggregateFunction;
import monasca.common.model.alarm.AlarmExpression;
import monasca.common.model.alarm.AlarmOperator;
import monasca.common.model.alarm.AlarmState;
import monasca.common.model.alarm.AlarmSubExpression;
import monasca.common.model.alarm.AlarmTransitionSubAlarm;
import monasca.common.model.metric.MetricDefinition;
import org.testng.annotations.Test;
@ -143,22 +146,20 @@ public class AlarmTest {
AlarmExpression expr =
new AlarmExpression(
"avg(hpcs.compute{instance_id=5,metric_name=cpu,device=1}, 1) > 5 times 3 OR avg(hpcs.compute{flavor_id=3,metric_name=mem}, 2) < 4 times 3");
SubAlarm subAlarm1 =
new SubAlarm("123", TEST_ALARM_ID, new SubExpression(UUID.randomUUID().toString(), expr
.getSubExpressions().get(0)));
SubAlarm subAlarm2 =
new SubAlarm("456", TEST_ALARM_ID, new SubExpression(UUID.randomUUID().toString(), expr
.getSubExpressions().get(1)));
List<String> expressions =
Arrays.asList(subAlarm1.getExpression().toString(), subAlarm2.getExpression().toString());
Alarm alarm = new Alarm();
List<AlarmTransitionSubAlarm> transitionSubAlarms = new ArrayList<>();
transitionSubAlarms.add(new AlarmTransitionSubAlarm(expr.getSubExpressions().get(0), AlarmState.UNDETERMINED, new ArrayList<Double>()));
transitionSubAlarms.add(new AlarmTransitionSubAlarm(expr.getSubExpressions().get(1), AlarmState.ALARM, new ArrayList<Double>()));
alarm.setTransitionSubAlarms(transitionSubAlarms);
assertEquals(
Alarm.buildStateChangeReason(AlarmState.UNDETERMINED, expressions),
"No data was present for the sub-alarms: [avg(hpcs.compute{device=1, instance_id=5, metric_name=cpu}, 1) > 5.0 times 3, avg(hpcs.compute{flavor_id=3, metric_name=mem}, 2) < 4.0 times 3]");
alarm.buildStateChangeReason(AlarmState.UNDETERMINED),
"No data was present for the sub-alarms: avg(hpcs.compute{device=1, instance_id=5, metric_name=cpu}, 1) > 5.0 times 3");
assertEquals(
Alarm.buildStateChangeReason(AlarmState.ALARM, expressions),
"Thresholds were exceeded for the sub-alarms: [avg(hpcs.compute{device=1, instance_id=5, metric_name=cpu}, 1) > 5.0 times 3, avg(hpcs.compute{flavor_id=3, metric_name=mem}, 2) < 4.0 times 3]");
alarm.buildStateChangeReason(AlarmState.ALARM),
"Thresholds were exceeded for the sub-alarms: avg(hpcs.compute{flavor_id=3, metric_name=mem}, 2) < 4.0 times 3 with the values: []");
}
/**

View File

@ -31,6 +31,7 @@ import monasca.common.model.alarm.AlarmExpression;
import monasca.common.model.alarm.AlarmState;
import monasca.common.model.alarm.AlarmSubExpression;
import monasca.common.streaming.storm.Streams;
import monasca.common.util.Serialization;
import monasca.thresh.domain.model.Alarm;
import monasca.thresh.domain.model.AlarmDefinition;
import monasca.thresh.domain.model.SubAlarm;
@ -49,6 +50,7 @@ import org.testng.annotations.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -62,7 +64,7 @@ public class AlarmThresholdingBoltTest {
private AlarmDefinition alarmDefinition;
private Alarm alarm;
private List<SubAlarm> subAlarms;
private String subAlarmJson;
private AlarmEventForwarder alarmEventForwarder;
private AlarmDAO alarmDAO;
private AlarmDefinitionDAO alarmDefinitionDAO;
@ -121,8 +123,10 @@ public class AlarmThresholdingBoltTest {
+ "\"alarmName\":\"Test CPU Alarm\","
+ "\"alarmDescription\":\"Description of Alarm\",\"oldState\":\"OK\",\"newState\":\"ALARM\","
+ "\"actionsEnabled\":true,"
+ "\"stateChangeReason\":\"Thresholds were exceeded for the sub-alarms: ["
+ subAlarm.getExpression().getExpression() + "]\"," + "\"severity\":\"LOW\",\"timestamp\":1395587091}}";
+ "\"stateChangeReason\":\"Thresholds were exceeded for the sub-alarms: "
+ subAlarm.getExpression().getExpression() + " with the values: []\"," + "\"severity\":\"LOW\","
+ "\"subAlarms\":[" + buildSubAlarmJson(alarm.getSubAlarms()) + "],"
+ "\"timestamp\":1395587091}}";
verify(alarmEventForwarder, times(1)).send(alarmJson);
verify(alarmDAO, times(1)).updateState(alarmId, AlarmState.ALARM);
@ -141,7 +145,13 @@ public class AlarmThresholdingBoltTest {
+ "\"alarmName\":\"Test CPU Alarm\","
+ "\"alarmDescription\":\"Description of Alarm\",\"oldState\":\"ALARM\",\"newState\":\"OK\","
+ "\"actionsEnabled\":true,"
+ "\"stateChangeReason\":\"The alarm threshold(s) have not been exceeded\",\"severity\":\"LOW\",\"timestamp\":1395587091}}";
+ "\"stateChangeReason\":\"The alarm threshold(s) have not been exceeded for the sub-alarms: "
+ subAlarm.getExpression().getExpression() + " with the values: [], "
+ subAlarms.get(1).getExpression().getExpression() + " with the values: [], "
+ subAlarms.get(2).getExpression().getExpression() + " with the values: []"
+ "\",\"severity\":\"LOW\","
+ "\"subAlarms\":[" + buildSubAlarmJson(alarm.getSubAlarms()) + "],"
+ "\"timestamp\":1395587091}}";
verify(alarmEventForwarder, times(1)).send(okJson);
verify(alarmDAO, times(1)).updateState(alarmId, AlarmState.OK);
}
@ -256,6 +266,19 @@ public class AlarmThresholdingBoltTest {
return alarmId;
}
private String buildSubAlarmJson(Collection<SubAlarm> subAlarms){
StringBuilder stringBuilder = new StringBuilder();
for(SubAlarm subAlarm: subAlarms){
if (stringBuilder.length() != 0) {
stringBuilder.append(",");
}
stringBuilder.append(Serialization.toJson(subAlarm.getExpression())).setCharAt(stringBuilder.length()-1, ',');
stringBuilder.append("\"subAlarmState\":\"").append(subAlarm.getState()).append("\",");
stringBuilder.append("\"currentValues\":").append(subAlarm.getCurrentValues()).append("}");
}
return stringBuilder.toString().replace("AlarmSubExpression","subAlarmExpression");
}
private void emitSubAlarmStateChange(String alarmId, final SubAlarm subAlarm, AlarmState state) {
// Create a copy so changing the state doesn't directly update the ones in the bolt
final SubAlarm toEmit =