diff --git a/java/monasca-common-hibernate/src/main/java/monasca/common/hibernate/db/SubAlarmDefinitionDb.java b/java/monasca-common-hibernate/src/main/java/monasca/common/hibernate/db/SubAlarmDefinitionDb.java index 65ba0c7c..be22d68b 100644 --- a/java/monasca-common-hibernate/src/main/java/monasca/common/hibernate/db/SubAlarmDefinitionDb.java +++ b/java/monasca-common-hibernate/src/main/java/monasca/common/hibernate/db/SubAlarmDefinitionDb.java @@ -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"; diff --git a/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmExpression.java b/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmExpression.java index bf8e2800..23980580 100644 --- a/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmExpression.java +++ b/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmExpression.java @@ -151,7 +151,7 @@ public class AlarmExpression { public List getSubExpressions() { if (subExpressions != null) return subExpressions; - List subExpressions = new ArrayList(); + List 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 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; diff --git a/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmSubExpression.java b/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmSubExpression.java index 018a232d..37df3be6 100644 --- a/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmSubExpression.java +++ b/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmSubExpression.java @@ -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(); diff --git a/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmSubExpressionListener.java b/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmSubExpressionListener.java index c648bd3d..3b313d54 100644 --- a/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmSubExpressionListener.java +++ b/java/monasca-common-model/src/main/java/monasca/common/model/alarm/AlarmSubExpressionListener.java @@ -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 elements = new ArrayList(); + 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(); + 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. diff --git a/java/monasca-common-model/src/main/resources/monasca/common/model/alarm/AlarmExpression.g4 b/java/monasca-common-model/src/main/resources/monasca/common/model/alarm/AlarmExpression.g4 index 9f382e7b..97f2ab65 100644 --- a/java/monasca-common-model/src/main/resources/monasca/common/model/alarm/AlarmExpression.g4 +++ b/java/monasca-common-model/src/main/resources/monasca/common/model/alarm/AlarmExpression.g4 @@ -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());}} - ; \ No newline at end of file + ; diff --git a/java/monasca-common-model/src/test/java/monasca/common/model/alarm/AlarmExpressionTest.java b/java/monasca-common-model/src/test/java/monasca/common/model/alarm/AlarmExpressionTest.java index 01838024..704115a7 100644 --- a/java/monasca-common-model/src/test/java/monasca/common/model/alarm/AlarmExpressionTest.java +++ b/java/monasca-common-model/src/test/java/monasca/common/model/alarm/AlarmExpressionTest.java @@ -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 dimensions = Maps.newHashMap(); + final ArrayList 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 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()); + } } diff --git a/java/monasca-common-model/src/test/java/monasca/common/model/alarm/AlarmSubExpressionTest.java b/java/monasca-common-model/src/test/java/monasca/common/model/alarm/AlarmSubExpressionTest.java index 98aab9dd..eb8726bd 100644 --- a/java/monasca-common-model/src/test/java/monasca/common/model/alarm/AlarmSubExpressionTest.java +++ b/java/monasca-common-model/src/test/java/monasca/common/model/alarm/AlarmSubExpressionTest.java @@ -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"); + } + }