/*
* Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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 the License.
*/
package org.hawkular.alerts.engine;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.hawkular.alerts.api.model.condition.AvailabilityCondition;
import org.hawkular.alerts.api.model.condition.CompareCondition;
import org.hawkular.alerts.api.model.condition.StringCondition;
import org.hawkular.alerts.api.model.condition.ThresholdCondition;
import org.hawkular.alerts.api.model.condition.ThresholdRangeCondition;
import org.hawkular.alerts.api.model.dampening.Dampening;
import org.hawkular.alerts.api.model.data.AvailabilityType;
import org.hawkular.alerts.api.model.data.Data;
import org.hawkular.alerts.api.model.event.Alert;
import org.hawkular.alerts.api.model.trigger.Trigger;
import org.hawkular.alerts.engine.impl.DroolsRulesEngineImpl;
import org.hawkular.alerts.engine.service.RulesEngine;
import org.jboss.logging.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
/**
* <p>
* Performance tests of RulesEngine implementation.
* </p>
* These tests are intended to be used to validate the rules design under heavy load of data and rules.
* These are unit tests, so the focus is on the rules engine process itself it is not tested the integration between
* other components.
*
* @author Lucas Ponce
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class PerfRulesEngineTest {
private static final Logger log = Logger.getLogger(PerfRulesEngineTest.class);
RulesEngine rulesEngine = new DroolsRulesEngineImpl();
List<Alert> alerts = new ArrayList<>();
Set<Dampening> pendingTimeouts = new HashSet<>();
TreeSet<Data> datums = new TreeSet<Data>();
@Before
public void before() {
PerfLogger perfLogger = new PerfLogger("PERFLOGGER");
rulesEngine.addGlobal("log", perfLogger);
rulesEngine.addGlobal("alerts", alerts);
rulesEngine.addGlobal("pendingTimeouts", pendingTimeouts);
}
@After
public void after() {
rulesEngine.reset();
alerts.clear();
pendingTimeouts.clear();
datums.clear();
}
private void perfThreshold(String test, int nDefinitions, int nData, int nQueue) throws Exception {
List definitions = new ArrayList();
for (int i = 0; i < nDefinitions; i++) {
Trigger tN = new Trigger("tenant", "trigger-" + i, "Threshold-LT");
ThresholdCondition tNc1 = new ThresholdCondition("tenant", "trigger-" + i,
"NumericData-" + i,
ThresholdCondition.Operator.LT, 10.0);
tN.setEnabled(true);
definitions.add(tN);
definitions.add(tNc1);
}
if (nQueue > 0) {
for (int i = 0; i < nData; i++) {
for (int j = 0; j < nQueue; j++) {
datums.add(Data.forNumeric("tenant", "NumericData-" + i, ((i * nQueue) + j) * 1000, 5.0));
}
}
} else {
for (int i = 0; i < nData; i++) {
datums.add(Data.forNumeric("tenant", "NumericData-" + i, i * 1000, 5.0));
}
}
rulesEngine.addFacts(definitions);
rulesEngine.addData(datums);
long start = System.currentTimeMillis();
rulesEngine.fire();
long stop = System.currentTimeMillis();
if (nQueue > 0) {
assert alerts.size() == nData * nQueue : alerts;
} else {
assert alerts.size() == nData : alerts;
}
report(test, nDefinitions, nData, start, stop);
}
private void perfRange(String test, int nDefinitions, int nData, int nQueue) throws Exception {
List definitions = new ArrayList();
for (int i = 0; i < nDefinitions; i++) {
Trigger tN = new Trigger("tenant", "trigger-" + i, "Range");
ThresholdRangeCondition tNc1 = new ThresholdRangeCondition("tenant", "trigger-" + i,
"NumericData-" + i,
ThresholdRangeCondition.Operator.INCLUSIVE,
ThresholdRangeCondition.Operator.INCLUSIVE,
10.0, 15.0,
true);
tN.setEnabled(true);
definitions.add(tN);
definitions.add(tNc1);
}
if (nQueue > 0) {
for (int i = 0; i < nData; i++) {
for (int j = 0; j < nQueue; j++) {
datums.add(Data.forNumeric("tenant", "NumericData-" + i, ((i * nQueue) + j) * 1000, 12.5));
}
}
} else {
for (int i = 0; i < nData; i++) {
datums.add(Data.forNumeric("tenant", "NumericData-" + i, i * 1000, 12.5));
}
}
rulesEngine.addFacts(definitions);
rulesEngine.addData(datums);
long start = System.currentTimeMillis();
rulesEngine.fire();
long stop = System.currentTimeMillis();
if (nQueue > 0) {
assert alerts.size() == nData * nQueue : alerts;
} else {
assert alerts.size() == nData : alerts;
}
report(test, nDefinitions, nData, start, stop);
}
private void perfCompare(String test, int nDefinitions, int nData, int nQueue) throws Exception {
List definitions = new ArrayList();
for (int i = 0; i < nDefinitions; i++) {
Trigger tN = new Trigger("tenant", "trigger-" + i, "Compare-D1-LT-Half-D2");
CompareCondition tNc1 = new CompareCondition("tenant", "trigger-" + i,
"NumericData-a-" + i,
CompareCondition.Operator.LT, 0.5, "NumericData-b-" + i);
tN.setEnabled(true);
definitions.add(tN);
definitions.add(tNc1);
}
if (nQueue > 0) {
for (int i = 0; i < nData; i++) {
for (int j = 0; j < nQueue; j++) {
datums.add(Data.forNumeric("tenant", "NumericData-a-" + i, ((i * nQueue) + j) * 1000, 10d));
datums.add(Data.forNumeric("tenant", "NumericData-b-" + i, ((i * nQueue) + j) * 1000, 30d));
}
}
} else {
for (int i = 0; i < nData; i++) {
datums.add(Data.forNumeric("tenant", "NumericData-a-" + i, i * 1000, 10d));
datums.add(Data.forNumeric("tenant", "NumericData-b-" + i, i * 1000, 30d));
}
}
rulesEngine.addFacts(definitions);
rulesEngine.addData(datums);
long start = System.currentTimeMillis();
rulesEngine.fire();
long stop = System.currentTimeMillis();
if (nQueue > 0) {
assert alerts.size() == nData * nQueue : alerts;
} else {
assert alerts.size() == nData : alerts;
}
report(test, nDefinitions, nData, start, stop);
}
private void perfString(String test, int nDefinitions, int nData, int nQueue) throws Exception {
List definitions = new ArrayList();
for (int i = 0; i < nDefinitions; i++) {
Trigger tN = new Trigger("tenant", "trigger-" + i, "String-StartsWith");
StringCondition tNc1 = new StringCondition("tenant", "trigger-" + i,
"StringData-" + i,
StringCondition.Operator.STARTS_WITH, "Fred", false);
tN.setEnabled(true);
definitions.add(tN);
definitions.add(tNc1);
}
if (nQueue > 0) {
for (int i = 0; i < nData; i++) {
for (int j = 0; j < nQueue; j++) {
datums.add(Data.forString("tenant", "StringData-" + i, (i * nQueue) + j, "Fred And Barney"));
}
}
} else {
for (int i = 0; i < nData; i++) {
datums.add(Data.forString("tenant", "StringData-" + i, i, "Fred And Barney"));
}
}
rulesEngine.addFacts(definitions);
rulesEngine.addData(datums);
long start = System.currentTimeMillis();
rulesEngine.fire();
long stop = System.currentTimeMillis();
if (nQueue > 0) {
assert alerts.size() == nData * nQueue : alerts;
} else {
assert alerts.size() == nData : alerts;
}
report(test, nDefinitions, nData, start, stop);
}
private void perfAvailability(String test, int nDefinitions, int nData, int nQueue) throws Exception {
List definitions = new ArrayList();
for (int i = 0; i < nDefinitions; i++) {
Trigger tN = new Trigger("tenant", "trigger-" + i, "Avail-DOWN");
AvailabilityCondition tNc1 = new AvailabilityCondition("tenant", "trigger-" + i, "AvailData-" + i,
AvailabilityCondition.Operator.NOT_UP);
tN.setEnabled(true);
definitions.add(tN);
definitions.add(tNc1);
}
if (nQueue > 0) {
for (int i = 0; i < nData; i++) {
for (int j = 0; j < nQueue; j++) {
datums.add(Data.forAvailability("tenant", "AvailData-" + i, (i * nQueue) + j,
AvailabilityType.DOWN));
}
}
} else {
for (int i = 0; i < nData; i++) {
datums.add(Data.forAvailability("tenant", "AvailData-" + i, i, AvailabilityType.DOWN));
}
}
rulesEngine.addFacts(definitions);
rulesEngine.addData(datums);
long start = System.currentTimeMillis();
rulesEngine.fire();
long stop = System.currentTimeMillis();
if (nQueue > 0) {
assert alerts.size() == nData * nQueue : alerts;
} else {
assert alerts.size() == nData : alerts;
}
report(test, nDefinitions, nData, start, stop);
}
private void perfMixedTwoConditions(String test, int nDefinitions, int nData, int nQueue) throws Exception {
List definitions = new ArrayList();
for (int i = 0; i < nDefinitions; i++) {
Trigger tN = new Trigger("tenant", "trigger-" + i, "Threshold-LT");
ThresholdCondition tNc1 = new ThresholdCondition("tenant", "trigger-" + i,
2,
1,
"NumericData-a-" + i,
ThresholdCondition.Operator.LT,
10.0);
ThresholdRangeCondition tNc2 = new ThresholdRangeCondition("tenant", "trigger-" + i, 2, 2,
"NumericData-b-" + i,
ThresholdRangeCondition.Operator.INCLUSIVE,
ThresholdRangeCondition.Operator.INCLUSIVE,
10.0,
15.0,
true);
tN.setEnabled(true);
definitions.add(tN);
definitions.add(tNc1);
definitions.add(tNc2);
}
if (nQueue > 0) {
for (int i = 0; i < nData; i++) {
for (int j = 0; j < nQueue; j++) {
datums.add(Data.forNumeric("tenant", "NumericData-a-" + i, ((i * nQueue) + j) * 1000, 5.0));
datums.add(Data.forNumeric("tenant", "NumericData-b-" + i, ((i * nQueue) + j) * 1000, 12.5));
}
}
} else {
for (int i = 0; i < nData; i++) {
datums.add(Data.forNumeric("tenant", "NumericData-a-" + i, i * 1000, 5.0));
datums.add(Data.forNumeric("tenant", "NumericData-b-" + i, i * 1000, 12.5));
}
}
rulesEngine.addFacts(definitions);
rulesEngine.addData(datums);
long start = System.currentTimeMillis();
rulesEngine.fire();
long stop = System.currentTimeMillis();
if (nQueue > 0) {
assert alerts.size() == nData * nQueue : alerts;
} else {
assert alerts.size() == nData : alerts;
}
report(test, nDefinitions, nData, start, stop);
}
private void perfMixedLargeConditions(String test, int nDefinitions, int nConditions, int nData, int nQueue)
throws Exception {
List definitions = new ArrayList();
for (int i = 0; i < nDefinitions; i++) {
Trigger tN = new Trigger("tenant", "trigger-" + i, "Threshold-LT");
definitions.add(tN);
for (int j = 1; j <= nConditions; j++) {
ThresholdCondition tNcj = new ThresholdCondition("tenant", "trigger-" + i,
nConditions,
j,
"NumericData-t" + i + "-c" + j,
ThresholdCondition.Operator.LT,
10.0);
definitions.add(tNcj);
}
tN.setEnabled(true);
}
if (nQueue > 0) {
for (int i = 0; i < nData; i++) {
for (int j = 0; j < nQueue; j++) {
for (int k = 1; k <= nConditions; k++) {
datums.add(Data.forNumeric("tenant", "NumericData-t" + i + "-c" + k, ((i * nQueue) + j) * 1000,
5.0));
}
}
}
} else {
for (int i = 0; i < nData; i++) {
for (int j = 1; j <= nConditions; j++) {
datums.add(Data.forNumeric("tenant", "NumericData-t" + i + "-c" + j, i * 1000, 5.0));
}
}
}
rulesEngine.addFacts(definitions);
rulesEngine.addData(datums);
long start = System.currentTimeMillis();
rulesEngine.fire();
long stop = System.currentTimeMillis();
if (nQueue > 0) {
assert alerts.size() == nData * nQueue : alerts;
} else {
assert alerts.size() == nData : alerts;
}
report(test, nDefinitions, nData, start, stop);
}
private void report(String description, int numDefinitions, int numData, long start, long stop) {
log.info("Report: " + description + " -- Definitions: " + numDefinitions + " -- Data: " + numData + " -- " +
"Total: " + (stop - start) + " ms ");
}
@Test
public void perf000ThresholdNoQueueSmall() throws Exception {
perfThreshold("perf000ThresholdNoQueueSmall", 1000, 1000, 0);
}
@Test
public void perf001ThresholdNoQueueMedium() throws Exception {
perfThreshold("perf001ThresholdNoQueueMedium", 5000, 5000, 0);
}
@Test
public void perf002ThresholdNoQueueLarge() throws Exception {
perfThreshold("perf002ThresholdNoQueueLarge", 10000, 10000, 0);
}
@Test
public void perf003RangeNoQueueSmall() throws Exception {
perfRange("perf003RangeNoQueueSmall", 1000, 1000, 0);
}
@Test
public void perf004RangeNoQueueMedium() throws Exception {
perfRange("perf004RangeNoQueueMedium", 5000, 5000, 0);
}
@Test
public void perf005RangeNoQueueLarge() throws Exception {
perfRange("perf005RangeNoQueueLarge", 10000, 10000, 0);
}
@Test
public void perf006CompareNoQueueSmall() throws Exception {
/*
We use 500 as nData as internally we feed 2 * nData so we compare same # objects inside the rule engine.
*/
perfCompare("perf006CompareNoQueueSmall", 1000, 500, 0);
}
@Test
public void perf007CompareNoQueueMedium() throws Exception {
/*
We use 2500 as nData as internally we feed 2 * nData so we compare same # objects inside the rule engine.
*/
perfCompare("perf007CompareNoQueueMedium", 5000, 2500, 0);
}
@Test
public void perf008CompareNoQueueLarge() throws Exception {
/*
We use 5000 as nData as internally we feed 2 * nData so we compare same # objects inside the rule engine.
*/
perfCompare("perf008CompareNoQueueLarge", 10000, 5000, 0);
}
@Test
public void perf009StringNoQueueSmall() throws Exception {
perfString("perf009StringNoQueueSmall", 1000, 1000, 0);
}
@Test
public void perf010StringNoQueueMedium() throws Exception {
perfString("perf010StringNoQueueMedium", 5000, 5000, 0);
}
@Test
public void perf011StringNoQueueLarge() throws Exception {
perfString("perf011StringNoQueueLarge", 10000, 10000, 0);
}
@Test
public void perf012AvailabilityNoQueueSmall() throws Exception {
perfAvailability("perf012AvailabilityNoQueueSmall", 1000, 1000, 0);
}
@Test
public void perf013AvailabilityNoQueueMedium() throws Exception {
perfAvailability("perf013AvailabilityNoQueueMedium", 5000, 5000, 0);
}
@Test
public void perf014AvailabilityNoQueueLarge() throws Exception {
perfAvailability("perf014AvailabilityNoQueueLarge", 10000, 10000, 0);
}
@Test
public void perf015MixedTwoConditionsNoQueueSmall() throws Exception {
/*
We use 500 as nData as internally we feed 2 * nData so we compare same # objects inside the rule engine.
*/
perfMixedTwoConditions("perf015MixedTwoConditionsNoQueueSmall", 1000, 1000, 0);
}
@Test
public void perf016MixedTwoConditionsNoQueueMedium() throws Exception {
/*
We use 2500 as nData as internally we feed 2 * nData so we compare same # objects inside the rule engine.
*/
perfMixedTwoConditions("perf016MixedTwoConditionsNoQueueMedium", 5000, 2500, 0);
}
@Test
public void perf017MixedTwoConditionsNoQueueLarge() throws Exception {
/*
We use 5000 as nData as internally we feed 2 * nData so we compare same # objects inside the rule engine.
*/
perfMixedTwoConditions("perf017MixedTwoConditionsNoQueueLarge", 10000, 5000, 0);
}
@Test
public void perf018ThresholdQueue() throws Exception {
/*
We have 10 data in the queue
*/
perfThreshold("perf018ThresholdQueue", 1000, 1000, 10);
}
@Test
public void perf019RangeQueue() throws Exception {
/*
We have 10 data in the queue
*/
perfRange("perf019RangeQueue", 1000, 1000, 10);
}
@Test
public void perf020CompareQueue() throws Exception {
/*
We have 10 data in the queue
*/
perfCompare("perf020CompareQueue", 1000, 1000, 10);
}
@Test
public void perf021StringQueue() throws Exception {
/*
We have 10 data in the queue
*/
perfCompare("perf021StringQueue", 1000, 1000, 10);
}
@Test
public void perf022AvailabilityQueue() throws Exception {
/*
We have 10 data in the queue
*/
perfCompare("perf022AvailabilityQueue", 1000, 1000, 10);
}
@Test
public void perf023LargeMixedConditionsNoQueueSmall() throws Exception {
/*
We have 25 conditions.
*/
perfMixedLargeConditions("perf023LargeMixedConditions", 1000, 25, 1000, 0);
}
/*
These tests require to increase the JVM setting.
As we want to run this perf test from travis we will maintain them disabled for future uses.
*/
// For manual testing
public void perf024LargeMixedConditionsNoQueueMedium() throws Exception {
/*
We have 25 conditions.
*/
perfMixedLargeConditions("perf023LargeMixedConditions", 5000, 25, 5000, 0);
}
// For manual testing
public void perf025LargeMixedConditionsQueueSmall() throws Exception {
/*
We have 25 conditions.
We have 10 data in the queue.
*/
perfMixedLargeConditions("perf023LargeMixedConditions", 1000, 25, 1000, 10);
}
// For manual testing
public void perf026LargeMixedConditionsQueueMedium() throws Exception {
/*
We have 25 conditions.
We have 10 data in the queue.
*/
perfMixedLargeConditions("perf023LargeMixedConditions", 5000, 25, 5000, 10);
}
public class PerfLogger extends Logger {
private static final long serialVersionUID = 1L;
public PerfLogger(String name) {
super(name);
}
@Override
protected void doLog(Level level,
String loggerClassName,
Object message,
Object[] parameters,
Throwable thrown) {
if (isEnabled(level)) {
try {
final String text = parameters == null || parameters.length == 0 ?
String.valueOf(message) : MessageFormat.format(String.valueOf(message), parameters);
log.log(level, text, thrown);
} catch (Throwable ignored) {
}
}
}
@Override
protected void doLogf(Level level,
String loggerClassName,
String format,
Object[] parameters,
Throwable thrown) {
if (isEnabled(level)) {
try {
final String text = parameters == null ? String.format(format) : String.format(format, parameters);
log.log(level, text, thrown);
} catch (Throwable ignored) {
}
}
}
@Override
public boolean isEnabled(Level level) {
if (level == Level.INFO || level == Level.WARN || level == Level.ERROR || level == Level.FATAL) {
return true;
}
return false;
}
}
}