(Non)deterministic support for alarms

Alarms can be created with deterministic keyword.
'deterministic' can be passed as part of alarm expression using:

 * keyword deterministic

If 'deterministic' is not found in expression it is assumed
to be 'false'.

Implements: blueprint alarmsonlogs
Change-Id: Ia42f9a1be37c31416bdac341b092fe527f860c16
This commit is contained in:
Tomasz Trębski 2016-03-15 08:47:39 +01:00
parent 91469d6ebf
commit b338b78907
7 changed files with 208 additions and 28 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 FUJITSU LIMITED
* Copyright 2015-2016 FUJITSU LIMITED
*
* 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
@ -29,6 +29,7 @@ import org.hibernate.annotations.OnDeleteAction;
import org.joda.time.DateTime;
import monasca.common.model.alarm.AlarmOperator;
import monasca.common.model.alarm.AlarmSubExpression;
@Entity
@Table(name = "sub_alarm_definition")
@ -81,6 +82,9 @@ public class SubAlarmDefinitionDb
@Column(name = "periods", length = 11, nullable = false)
private Integer periods;
@Column(name = "is_deterministic", length = 1, nullable = false)
private boolean deterministic = AlarmSubExpression.DEFAULT_DETERMINISTIC;
public SubAlarmDefinitionDb() {
super();
}
@ -95,6 +99,21 @@ public class SubAlarmDefinitionDb
Integer periods,
DateTime created_at,
DateTime updated_at) {
this(id, alarmDefinition, function, metricName, operator, threshold, period, periods, created_at,
updated_at, AlarmSubExpression.DEFAULT_DETERMINISTIC);
}
public SubAlarmDefinitionDb(String id,
AlarmDefinitionDb alarmDefinition,
String function,
String metricName,
String operator,
Double threshold,
Integer period,
Integer periods,
DateTime created_at,
DateTime updated_at,
boolean deterministic) {
super(id, created_at, updated_at);
this.alarmDefinition = alarmDefinition;
this.function = function;
@ -103,6 +122,7 @@ public class SubAlarmDefinitionDb
this.threshold = threshold;
this.period = period;
this.periods = periods;
this.deterministic = deterministic;
}
public SubAlarmDefinitionDb setPeriods(final Integer periods) {
@ -172,6 +192,15 @@ public class SubAlarmDefinitionDb
return this.periods;
}
public boolean isDeterministic() {
return this.deterministic;
}
public SubAlarmDefinitionDb setDeterministic(final boolean isDeterministic) {
this.deterministic = isDeterministic;
return this;
}
public interface Queries {
String BY_ALARMDEFINITION_ID = "SubAlarmDefinition.byAlarmDefinitionId";
String BY_ALARMDEFINITIONDIMENSION_SUBEXPRESSION_ID = "SubAlarmDefinition.byAlarmDefinitionDimension.subExpressionId";

View File

@ -151,7 +151,7 @@ public class AlarmExpression {
public List<AlarmSubExpression> getSubExpressions() {
if (subExpressions != null)
return subExpressions;
List<AlarmSubExpression> subExpressions = new ArrayList<AlarmSubExpression>();
List<AlarmSubExpression> subExpressions = new ArrayList<>(elements.size());
for (Object element : elements)
if (element instanceof AlarmSubExpression)
subExpressions.add((AlarmSubExpression) element);
@ -159,6 +159,30 @@ public class AlarmExpression {
return subExpressions;
}
/**
* Returns if expression is deterministic or non-deterministic.
*
* All {@link AlarmSubExpression} must be deterministic in order for entire expression
* to be such. Otherwise expression is non-deterministic.
*
* @return true/false
*
* @see #getSubExpressions()
* @see AlarmSubExpression#DEFAULT_DETERMINISTIC
*/
public boolean isDeterministic() {
final List<AlarmSubExpression> subExpressions = this.getSubExpressions();
if (subExpressions == null || subExpressions.isEmpty()) {
return AlarmSubExpression.DEFAULT_DETERMINISTIC;
}
for (final AlarmSubExpression alarmSubExpression : subExpressions) {
if (!alarmSubExpression.isDeterministic()) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
* Copyright 2016 FUJITSU LIMITED
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,17 +22,13 @@ import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import com.fasterxml.jackson.annotation.JsonCreator;
import monasca.common.model.alarm.AlarmExpressionLexer;
import monasca.common.model.alarm.AlarmExpressionParser;
import monasca.common.model.metric.MetricDefinition;
/**
@ -41,6 +38,7 @@ public class AlarmSubExpression implements Serializable {
private static final long serialVersionUID = -7458129503846747592L;
public static final int DEFAULT_PERIOD = 60;
public static final int DEFAULT_PERIODS = 1;
public static final boolean DEFAULT_DETERMINISTIC = false;
private AggregateFunction function;
private MetricDefinition metricDefinition;
@ -48,6 +46,8 @@ public class AlarmSubExpression implements Serializable {
private double threshold;
private int period;
private int periods;
private boolean deterministic = DEFAULT_DETERMINISTIC;
// Use a DecimalFormatter for threshold because the standard double format starts using scientific notation when
// threshold is very large and that scientific notation can't be parsed when recreating the SubExpression
private static final DecimalFormat formatter;
@ -57,14 +57,29 @@ public class AlarmSubExpression implements Serializable {
formatter.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.US));
}
public AlarmSubExpression(AggregateFunction function, MetricDefinition metricDefinition,
AlarmOperator operator, double threshold, int period, int periods) {
public AlarmSubExpression(AggregateFunction function,
MetricDefinition metricDefinition,
AlarmOperator operator,
double threshold,
int period,
int periods) {
this(function, metricDefinition, operator, threshold, period, periods, DEFAULT_DETERMINISTIC);
}
public AlarmSubExpression(AggregateFunction function,
MetricDefinition metricDefinition,
AlarmOperator operator,
double threshold,
int period,
int periods,
boolean deterministic) {
this.function = function;
this.metricDefinition = metricDefinition;
this.operator = operator;
this.threshold = threshold;
this.period = period;
this.periods = periods;
this.deterministic = deterministic;
}
AlarmSubExpression() {
@ -111,6 +126,8 @@ public class AlarmSubExpression implements Serializable {
return false;
if (periods != other.periods)
return false;
if (deterministic != other.deterministic)
return false;
if (Double.doubleToLongBits(threshold) != Double.doubleToLongBits(other.threshold))
return false;
return true;
@ -130,8 +147,12 @@ public class AlarmSubExpression implements Serializable {
public String getExpression() {
StringBuilder sb = new StringBuilder();
sb.append(function).append('(').append(metricDefinition.toExpression());
if (period != 60)
if (this.isDeterministic()) {
sb.append(", deterministic"); // present only non-default value
}
if (period != 60) {
sb.append(", ").append(period);
}
sb.append(") ").append(operator).append(' ').append(formatter.format(threshold));
if (periods != 1)
sb.append(" times ").append(periods);
@ -162,6 +183,10 @@ public class AlarmSubExpression implements Serializable {
return threshold;
}
public boolean isDeterministic() {
return this.deterministic;
}
@Override
public int hashCode() {
final int prime = 31;
@ -171,6 +196,7 @@ public class AlarmSubExpression implements Serializable {
result = prime * result + ((operator == null) ? 0 : operator.hashCode());
result = prime * result + period;
result = prime * result + periods;
result = prime * result + Boolean.valueOf(this.deterministic).hashCode();
long temp;
temp = Double.doubleToLongBits(threshold);
result = prime * result + (int) (temp ^ (temp >>> 32));
@ -201,6 +227,10 @@ public class AlarmSubExpression implements Serializable {
this.threshold = threshold;
}
public void setDeterministic(final boolean deterministic) {
this.deterministic = deterministic;
}
@Override
public String toString() {
return getExpression();

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
* Copyright 2016 FUJITSU LIMITED
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -27,6 +28,7 @@ import monasca.common.model.metric.MetricDefinition;
* Complex alarm parser lister for sub expression extraction.
*/
class AlarmSubExpressionListener extends AlarmExpressionBaseListener {
private final boolean simpleExpression;
private AggregateFunction function;
private String namespace;
@ -36,23 +38,36 @@ class AlarmSubExpressionListener extends AlarmExpressionBaseListener {
private int period = AlarmSubExpression.DEFAULT_PERIOD;
private int periods = AlarmSubExpression.DEFAULT_PERIODS;
private List<Object> elements = new ArrayList<Object>();
private boolean deterministic = AlarmSubExpression.DEFAULT_DETERMINISTIC;
AlarmSubExpressionListener(boolean simpleExpression) {
this.simpleExpression = simpleExpression;
}
private void saveSubExpression() {
AlarmSubExpression subExpression = new AlarmSubExpression(function, new MetricDefinition(
namespace, dimensions), operator, threshold, period, periods);
// not possible to establish if metric is sporadic from expression, so we go with default
final MetricDefinition metricDefinition = new MetricDefinition(
namespace,
dimensions
);
final AlarmSubExpression subExpression = new AlarmSubExpression(function,
metricDefinition,
operator,
threshold,
period,
periods,
deterministic
);
elements.add(subExpression);
function = null;
namespace = null;
dimensions = new TreeMap<String, String>();
dimensions = new TreeMap<>();
operator = null;
threshold = 0;
period = AlarmSubExpression.DEFAULT_PERIOD;
periods = AlarmSubExpression.DEFAULT_PERIODS;
deterministic = AlarmSubExpression.DEFAULT_DETERMINISTIC;
}
@Override
@ -155,6 +170,11 @@ class AlarmSubExpressionListener extends AlarmExpressionBaseListener {
elements.add(BooleanOperator.AND);
}
@Override
public void enterDeterministic(final AlarmExpressionParser.DeterministicContext ctx) {
this.deterministic = true;
}
/**
* Returns the operator and operand elements of the expression in postfix order. Elements will be
* of types AlarmSubExpression and BooleanOperator.

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
* Copyright 2016 FUJITSU LIMITED
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,7 +32,7 @@ expression
function
: functionType '(' compoundIdentifier (',' period)? ')'
: functionType '(' compoundIdentifier (',' deterministic)? (',' period)? ')'
;
relational_operator
@ -118,6 +119,10 @@ period
: INTEGER
;
deterministic
: 'deterministic'
;
literal
: DECIMAL
| INTEGER
@ -133,6 +138,7 @@ txt
| INTEGER
| STRING
;
LT
: [lL][tT]
;
@ -226,4 +232,4 @@ WS : [ \t\r\n]+ -> skip ;
FALL_THROUGH
: . {if(true) {throw new IllegalArgumentException("IllegalCharacter: " + getText());}}
;
;

View File

@ -1,11 +1,12 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* Copyright 2016 FUJITSU LIMITED
*
* 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
@ -19,11 +20,14 @@ import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.testng.annotations.Test;
import com.beust.jcommander.internal.Maps;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.testng.annotations.Test;
import monasca.common.model.metric.MetricDefinition;
@ -315,4 +319,60 @@ public class AlarmExpressionTest {
+ "(min(ເຮືອນ{dn3=dv3,家=дом}) < 10 or sum(biz{dn5=dv5}) >9 and "
+ "count(fizzle) lt 0 or count(baz) > 1)");
}
public void shouldParseDeterministicExpression() {
final Map<String, String> dimensions = Maps.newHashMap();
final ArrayList<AlarmExpression> expressions = Lists.newArrayList(
new AlarmExpression("count(log.error{},deterministic,20) > 5")
);
final MetricDefinition metricDefinition = new MetricDefinition("log.error", dimensions);
final AlarmSubExpression logErrorExpr = new AlarmSubExpression(
AggregateFunction.COUNT,
metricDefinition,
AlarmOperator.GT,
5,
20,
1,
true // each expression is deterministic
);
for (final AlarmExpression expr : expressions) {
final List<AlarmSubExpression> subExpressions = expr.getSubExpressions();
assertTrue(expr.isDeterministic()); // each expression is deterministic
assertEquals(1, subExpressions.size());
assertEquals(subExpressions.get(0), logErrorExpr);
}
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldNotParseInvalidExpressionWrongRightOperand() {
AlarmExpression.of("count(log.error{},deterministic=foo,20) > 5");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldNotParseInvalidExpressionMalformedDeterministicKeyword() {
AlarmExpression.of("count(log.error{},determ=true,20) > 5");
}
public void shouldBeDeterministicIfAllSubExpressionAreDeterministic() {
final String expression1 = "count(log.error{hostname=1,component=A},deterministic,20) > 5";
final String expression2 = "count(log.error{hostname=1,component=B},deterministic,20) > 10";
final String expression3 = "count(log.error{hostname=1,component=C},deterministic,20) > 15";
final String expression = String.format("%s OR %s OR %s", expression1, expression2, expression3);
assertTrue(new AlarmExpression(expression).isDeterministic());
}
public void shouldBeNonDeterministicIfAtLeastOneExpressionIsNonDeterministic() {
final String expression1 = "count(log.error{hostname=1,component=A},deterministic,20) > 5";
final String expression2 = "count(log.error{hostname=1,component=B},deterministic,20) > 10";
final String expression3 = "count(log.error{}) > 15";
final String expression = String.format("%s OR %s OR %s", expression1, expression2, expression3);
assertFalse(new AlarmExpression(expression).isDeterministic());
}
}

View File

@ -1,11 +1,12 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* Copyright 2016 FUJITSU LIMITED
*
* 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
@ -17,13 +18,9 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import com.google.common.collect.ImmutableMap;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import monasca.common.model.alarm.AggregateFunction;
import monasca.common.model.alarm.AlarmExpression;
import monasca.common.model.alarm.AlarmOperator;
import monasca.common.model.alarm.AlarmSubExpression;
import monasca.common.model.metric.MetricDefinition;
@Test
@ -172,4 +169,18 @@ public class AlarmSubExpressionTest {
public void shouldAllowDecimalThresholds() {
assertEquals(AlarmSubExpression.of("avg(hpcs.compute) > 2.375").getThreshold(), 2.375);
}
public void shouldBeNonDeterministicByDefault() {
assertFalse(AlarmSubExpression.of("count(log.error{}) > 1.0").isDeterministic());
}
public void shouldBeDeterministicIfSet() {
assertTrue(AlarmSubExpression.of("count(log.error{}, deterministic) > 1.0").isDeterministic());
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldFailWithMalformedDeterministicKeyword() {
AlarmSubExpression.of("count(log.error{}, deterministici) > 1.0");
}
}