Last active
October 6, 2024 10:54
-
-
Save dacr/12dbb61d923b38a4694379e6be8d0086 to your computer and use it in GitHub Desktop.
Drools understanding events base knowledge base / published by https://github.com/dacr/code-examples-manager #d4fe0ba1-43aa-433a-b03c-9806cc5b1081/eb056afca1acd91a69cc5bf8e6ddd69eced9f528
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// summary : Drools understanding events base knowledge base | |
// keywords : scala, drools, mvel, scalatest, ai, knowledgebase, events, @testable | |
// publish : gist | |
// authors : David Crosson | |
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2) | |
// id : d4fe0ba1-43aa-433a-b03c-9806cc5b1081 | |
// created-on : 2019-10-06T21:12:41+02:00 | |
// managed-by : https://github.com/dacr/code-examples-manager | |
// run-with : scala-cli $file | |
// --------------------- | |
//> using scala "3.5.1" | |
//> using dep "fr.janalyse::drools-scripting:1.2.0" | |
//> using dep "org.scalatest::scalatest:3.2.19" | |
//> using objectWrapper | |
// --------------------- | |
import java.time.Instant | |
import fr.janalyse.droolscripting.* | |
import org.scalatest.* | |
import flatspec.* | |
import matchers.* | |
import OptionValues.* | |
class UnderstandingEventsRules extends AnyFlatSpec with should.Matchers { | |
override def suiteName: String = "UnderstandingEventsRules" | |
// ====================================================================== | |
"DROOLS" should "be able to delay rule execution" in { | |
val drl = | |
"""package test //#EEX1 | |
|rule "init" | |
|duration 10000 // 10 seconds | |
|when then insert("OK"); end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.fireAllRules() | |
engine.strings should have size (0) | |
engine.timeShiftInSeconds(12) | |
engine.fireAllRules() | |
engine.strings should have size (1) | |
} | |
// ====================================================================== | |
it should "be able to define a point-in-time event" in { | |
val drl = | |
"""package test //#EEX2 | |
|declare CpuPeak @role(event) | |
| value:double | |
|end | |
|rule "init" when then insert(new CpuPeak(95.0)); end | |
|//----------------- | |
|rule "show event" when $peak:CpuPeak() then | |
| insert("CPU peak reached :"+$peak.toString()); | |
|end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.fireAllRules() | |
info("In that case the timestamp is automatically inserted, initialized with current time") | |
val result = engine.strings.headOption.value | |
result.toString should startWith regex "CPU peak reached" | |
} | |
// ====================================================================== | |
it should "be able to define an interval-based event" in { | |
val drl = | |
"""package test //#EEX3 | |
|declare Processed @role(event) @duration(duration) | |
| name:String | |
| duration:long | |
|end | |
|rule "init" when then insert(new Processed("Cooking", 500)); end | |
|//----------------- | |
|rule "show event" when Processed($name:name, $duration:duration) then | |
| insert($name+" has been processed in"+$duration); | |
|end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.fireAllRules() | |
info("In that case the timestamp is also automatically inserted, initialized with current time") | |
val result = engine.strings.headOption.value | |
result.toString should include regex ".* has been processed" | |
val processed = engine.getModelFirstInstance("test.Processed").value | |
engine | |
.getModelInstanceAttribute(processed, "duration") | |
.collect { case l: java.lang.Long => l } | |
.value shouldBe 500 | |
} | |
// ====================================================================== | |
it should "be possible for a point-in-time event to expire" in { | |
val drl = | |
"""package test //#EEX4 | |
|declare SomethingHappened @role(event) @expires(5s) | |
| message:String | |
|end | |
|rule "init" when then insert(new SomethingHappened("Bad stuff")); end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.fireAllRules() | |
engine.getModelInstances("test.SomethingHappened").toList should have size (1) | |
engine.timeShiftInSeconds(4) // t+4s | |
engine.fireAllRules() | |
engine.getModelInstances("test.SomethingHappened").toList should have size (1) | |
engine.timeShiftInSeconds(2) // t+6s | |
engine.fireAllRules() | |
engine.getModelInstances("test.SomethingHappened").toList should have size (0) | |
info("event expire is a convenient way to keep memory usage under control") | |
} | |
// ====================================================================== | |
it should "be possible for an interval-based event to expire" in { | |
val drl = | |
"""package test //#EEX5 | |
|declare SomethingHappened @role(event) @expires(5s) @duration(duration) | |
| message:String | |
| duration:long | |
|end | |
|rule "init" when then insert(new SomethingHappened("Bad stuff",10000)); end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.fireAllRules() | |
engine.getModelInstances("test.SomethingHappened").toList should have size (1) | |
engine.timeShiftInSeconds(4) // t+4s | |
engine.fireAllRules() | |
engine.getModelInstances("test.SomethingHappened").toList should have size (1) | |
engine.timeShiftInSeconds(2) // t+6s | |
engine.fireAllRules() | |
engine.getModelInstances("test.SomethingHappened").toList should have size (1) | |
engine.timeShiftInSeconds(8) // t+14s | |
engine.fireAllRules() | |
engine.getModelInstances("test.SomethingHappened").toList should have size (1) | |
engine.timeShiftInSeconds(2) // t+16s | |
engine.fireAllRules() | |
engine.getModelInstances("test.SomethingHappened").toList should have size (0) | |
info("An interval-based event expires at (startTimestamp + duration + expires ) > currentClock !") | |
info("which corresponds to (endTimestamp + expires) > currentClock !") | |
} | |
// ====================================================================== | |
it should "be able to define an event using a custom date" in { | |
val drl = | |
"""package test //#EEX6 | |
|declare CpuPeak @role(event) @timestamp(timestamp) | |
| timestamp: java.util.Date | |
| value:double | |
|end | |
|//----------------- | |
|rule "show event" when $peak:CpuPeak() then | |
| insert("CPU peak reached :"+$peak.toString()); | |
|end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.insertJson("""{"timestamp":"2019-01-01T14:00:00Z", "value":95.0}""", "test.CpuPeak") | |
engine.fireAllRules() | |
val result = engine.strings.headOption.value | |
result.toString should startWith regex "CPU peak reached" | |
} | |
// ====================================================================== | |
it should "be able to search for two consecutive point-in-time events" in { | |
val drl = | |
"""package test //#EEX7 | |
|declare CpuPeak @role(event) @timestamp(timestamp) | |
| timestamp: java.util.Date | |
| value:double | |
|end | |
|//----------------- | |
|rule "show event" when | |
| $peak1:CpuPeak() | |
|// $peak2:CpuPeak(this after $peak1) | |
| $peak2:CpuPeak($peak1 before this) | |
|then | |
| insert("CPU peak reached :"+$peak1.toString()); | |
|end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.insertJson("""{"timestamp":"2019-01-01T14:00:00Z", "value":95.0}""", "test.CpuPeak") | |
engine.insertJson("""{"timestamp":"2019-01-01T15:00:00Z", "value":95.0}""", "test.CpuPeak") | |
engine.fireAllRules() | |
val result = engine.strings.headOption.value | |
result.toString should startWith regex "CPU peak reached" | |
} | |
// ====================================================================== | |
it should "be able to search for two consecutive near-by point-in-time events" in { | |
val drl = | |
"""package test //#EEX8 | |
|declare CpuPeak @role(event) @timestamp(timestamp) | |
| timestamp: java.util.Date | |
| value:double | |
|end | |
|//----------------- | |
|rule "show event" when | |
| $peak1:CpuPeak($value1:value, value > 90) | |
| $peak2:CpuPeak($value2:value,value > 90, this after[0,15m] $peak1, this != $peak1 ) | |
|then | |
| insert("Too many CPU peaks : "+$value1+" "+$value2); | |
|end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.insertJson("""{"timestamp":"2019-01-01T14:00:00Z", "value":95.0}""", "test.CpuPeak") | |
engine.insertJson("""{"timestamp":"2019-01-01T15:00:00Z", "value":96.0}""", "test.CpuPeak") | |
engine.insertJson("""{"timestamp":"2019-01-01T15:10:00Z", "value":97.0}""", "test.CpuPeak") | |
engine.fireAllRules() | |
val results = engine.strings | |
results should have size (1) | |
results.headOption.value shouldBe "Too many CPU peaks : 96.0 97.0" | |
} | |
// ====================================================================== | |
it should "be able to search for two consecutive near-by point-in-time events using epochs timestamp" in { | |
val drl = | |
"""package test //#EEX8B | |
|declare CpuPeak @role(event) @timestamp(timestamp) | |
| timestamp: long | |
| value:double | |
|end | |
|//----------------- | |
|rule "show event" when | |
| $peak1:CpuPeak($value1:value, value > 90) | |
| $peak2:CpuPeak($value2:value,value > 90, this after[0,15m] $peak1, this != $peak1 ) | |
|then | |
| insert("Too many CPU peaks : "+$value1+" "+$value2); | |
|end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
def epochOf(timestamp: String): Long = Instant.parse(timestamp).toEpochMilli | |
engine.insertJson(s"""{"timestamp":${epochOf("2019-01-01T14:00:00Z")}, "value":95.0}""", "test.CpuPeak") | |
engine.insertJson(s"""{"timestamp":${epochOf("2019-01-01T15:00:00Z")}, "value":96.0}""", "test.CpuPeak") | |
engine.insertJson(s"""{"timestamp":${epochOf("2019-01-01T15:10:00Z")}, "value":97.0}""", "test.CpuPeak") | |
engine.fireAllRules() | |
val results = engine.strings | |
results should have size (1) | |
results.headOption.value shouldBe "Too many CPU peaks : 96.0 97.0" | |
} | |
// ====================================================================== | |
it should "be able to search for two consecutives near-by point-in-time events" in { | |
val drl = | |
"""package test //#EEX8C | |
|declare CpuPeak @role(event) @timestamp(timestamp) | |
| timestamp: java.util.Date | |
| value:double | |
|end | |
|//----------------- | |
|rule "show event" when | |
| $peak1:CpuPeak($value1:value, value > 90) | |
| $peak2:CpuPeak($value2:value,value > 90, this after[30m] $peak1, this != $peak1 ) | |
|then | |
| insert("Too many CPU peaks : "+$value1+" "+$value2); | |
|end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.insertJson("""{"timestamp":"2019-01-01T14:00:00Z", "value":95.0}""", "test.CpuPeak") | |
engine.insertJson("""{"timestamp":"2019-01-01T15:00:00Z", "value":96.0}""", "test.CpuPeak") | |
engine.insertJson("""{"timestamp":"2019-01-01T15:10:00Z", "value":97.0}""", "test.CpuPeak") | |
engine.fireAllRules() | |
val results = engine.strings | |
results should have size (2) | |
results should contain allOf("Too many CPU peaks : 95.0 96.0", "Too many CPU peaks : 95.0 97.0") | |
} | |
// ====================================================================== | |
it should "be able to search for two consecutive near-by point-in-time events with server distinctions" in { | |
val drl = | |
"""package test //#EEX8D | |
|declare CpuPeak @role(event) @timestamp(timestamp) | |
| server:String | |
| timestamp: java.util.Date | |
| value:double | |
|end | |
|//----------------- | |
|rule "show event" when | |
| $peak1:CpuPeak($value1:value, value > 90, $server1:server) | |
| $peak2:CpuPeak($value2:value, value > 90, this after[0,15m] $peak1, this != $peak1, server == $server1 ) | |
|then | |
| insert("Too many CPU peaks : "+$value1+" "+$value2); | |
|end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.insertJson("""{"server":"server2", "timestamp":"2019-01-01T15:05:00Z", "value":95.0}""", "test.CpuPeak") | |
engine.insertJson("""{"server":"server1", "timestamp":"2019-01-01T15:00:00Z", "value":96.0}""", "test.CpuPeak") | |
engine.insertJson("""{"server":"server1", "timestamp":"2019-01-01T15:10:00Z", "value":97.0}""", "test.CpuPeak") | |
engine.fireAllRules() | |
val results = engine.strings | |
results should have size (1) | |
results.headOption.value shouldBe "Too many CPU peaks : 96.0 97.0" | |
} | |
// ====================================================================== | |
it should "be possible to compute a time-range event from two point-in-time events" in { | |
val drl = | |
"""package test //#EEX9 | |
|declare ProcessingStarted @role(event) @timestamp(timestamp) | |
| timestamp: java.util.Date | |
| name:String | |
|end | |
|declare ProcessingEnded @role(event) @timestamp(timestamp) | |
| timestamp: java.util.Date | |
| name:String | |
|end | |
|declare ProcessingDuration @role(event) @timestamp(timestamp) | |
| timestamp: java.util.Date | |
| howLong:Long | |
| name:String | |
|end | |
|//----------------- | |
|rule "show event" when | |
| $start:ProcessingStarted($ds:timestamp, $ts:timestamp.time, $name:name) | |
| $end:ProcessingEnded( this after $start, $te:timestamp.time, name==$name) | |
|then | |
| insert(new ProcessingDuration($ds, $te-$ts, $name)); | |
|end | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.insertJson("""{"timestamp":"2019-01-01T14:00:00Z", "name":"A"}""", "test.ProcessingStarted") | |
engine.insertJson("""{"timestamp":"2019-01-01T15:00:00Z", "name":"A"}""", "test.ProcessingEnded") | |
engine.fireAllRules() | |
val result = engine.getModelFirstInstanceAttribute("test.ProcessingDuration", "howLong") | |
result.collect { case x: java.lang.Long => x }.value shouldBe 3600 * 1000L // make it fail with : +1 | |
info("This is an important example, a typical use case") | |
info("AND remember to use expires to control your working memory size !") | |
} | |
// ====================================================================== | |
it should "be able to catch recent events based on a sliding time window" in { | |
val drl = | |
"""package test //EEX10A | |
| | |
|declare Event @role(event) | |
| id:int | |
| name:String | |
|end | |
| | |
|rule "recent event" | |
|when | |
| $event:Event($id:id, $name:name) over window:time(15m) | |
|then | |
| insert("OK-"+$id); | |
|end | |
| | |
|""".stripMargin | |
val e = DroolsEngine(drl, DroolsEngineConfig.configWithEquality) | |
e.fireAllRules() | |
e.strings should have size (0) | |
for {id <- 1 to 10} { | |
e.advanceTimeMinutes(5) | |
val event = s"""{"id":$id, "name":"something has happened! #$id"}""" | |
val eventType = "test.Event" | |
e.insertJson(event, eventType) | |
} | |
e.fireAllRules() | |
info("It reacts on events of the last 15mn!!! because window:time(15m)") | |
e.strings should have size (3) | |
e.strings should contain allOf("OK-10", "OK-9", "OK-8") | |
} | |
// ====================================================================== | |
it should "be able to catch recent events based on a sliding length window" in { | |
val drl = | |
"""package test //EEX10B | |
| | |
|declare AlarmEvent @role(event) | |
| id:int | |
| name:String | |
| criticity:int | |
|end | |
| | |
|rule "recent alarm event" | |
|when | |
| AlarmEvent($id:id, $name:name, $mycrit:criticity, criticity>10) over window:length(2) | |
|then | |
| insert("OK-"+$id+"-"+$mycrit); | |
|end | |
| | |
|""".stripMargin | |
val e = DroolsEngine(drl, DroolsEngineConfig.configWithEquality) | |
e.fireAllRules() | |
e.strings should have size (0) | |
for {id <- 1 to 10} { | |
e.advanceTimeMinutes(5) | |
val criticity = if (id % 2 == 0) 100 else 1 | |
val event = s"""{"id":$id, "name":"something has happened! #$id", "criticity":$criticity}""" | |
val eventType = "test.AlarmEvent" | |
e.insertJson(event, eventType) | |
} | |
e.fireAllRules() | |
info("It reacts on the three last events !!! because window:length(2)") | |
e.strings should have size (2) | |
e.strings should contain allOf("OK-8-100", "OK-10-100") | |
} | |
// ====================================================================== | |
it should "be able to detect too many events in a short period of time" in { | |
info("first buggy implementation - the rules fired too many times - see next TU") | |
val drl = | |
"""package test //#EEX11 | |
| | |
|import java.util.LinkedList | |
|global org.slf4j.Logger logger | |
| | |
|declare StartEvent @role(event) @expires(48h) | |
| host:String | |
| name:String | |
|end | |
| | |
|rule "Too many restart" | |
|when | |
| $event:StartEvent($host:host, $name:name) | |
| $events:LinkedList(size>5) from collect ( | |
| StartEvent( | |
| this != $event, | |
| host==$host, | |
| name==$name, | |
| $event after[0s, 10h] this) | |
| ) | |
|then | |
| //System.out.println("SIZE EVENTS"+$events.size()); | |
| insert("OK"); | |
|end | |
|""".stripMargin | |
val e = DroolsEngine(drl, DroolsEngineConfig.configWithEquality) | |
e.fireAllRules() | |
e.strings should have size (0) | |
val event = """{"host":"zorglub", "name":"apache"}""" | |
val eventType = "test.StartEvent" | |
(1 to 10).foreach { _ => e.insertJson(event, eventType); e.advanceTimeHours(5) } | |
e.fireAllRules() | |
e.strings should have size (0) | |
(1 to 10).foreach { _ => e.insertJson(event, eventType); e.advanceTimeMinutes(10) } | |
e.fireAllRules() | |
e.strings should have size (1) | |
} | |
// ====================================================================== | |
it should "be able to detect too many events in a short period of time revisited" in { | |
val drl = | |
"""package test //#EEX11B | |
| | |
|import java.util.LinkedList | |
|global org.slf4j.Logger logger | |
| | |
|declare StartEvent @role(event) @expires(48h) | |
| host:String | |
| name:String | |
|end | |
| | |
|rule "Too many restart" | |
|when | |
| $event:StartEvent($host:host, $name:name) over window:length(1) | |
| //$event:StartEvent($host:host, $name:name) over window:time(5m) | |
| $events:LinkedList(size>5) from collect ( | |
| StartEvent( | |
| this != $event, | |
| host==$host, | |
| name==$name, | |
| this before[0s, 10h] $event) | |
| ) | |
|then | |
| insert(new String("OK-"+$events.size())); | |
| //insert(new String("OK")); | |
|end | |
|""".stripMargin | |
val e = DroolsEngine(drl, DroolsEngineConfig.configWithIdentity) | |
e.fireAllRules() | |
e.strings should have size (0) | |
val event = """{"host":"zorglub", "name":"apache"}""" | |
val eventType = "test.StartEvent" | |
(1 to 10).foreach { _ => e.insertJson(event, eventType); e.advanceTimeHours(5) } | |
e.fireAllRules() | |
e.strings should have size (0) | |
(1 to 10).foreach { _ => e.insertJson(event, eventType); e.advanceTimeMinutes(10) } | |
e.fireAllRules() | |
e.strings should have size (1) | |
} | |
// ====================================================================== | |
"DROOLS" should "be able to detect too many events in a short period of time revisited again" in { | |
val drl = | |
"""package test //#EEX11C | |
| | |
|import java.util.List | |
|global org.slf4j.Logger logger | |
| | |
|declare StartEvent @role(event) @expires(48h) | |
| host:String | |
| name:String | |
|end | |
| | |
|rule "Too many restart" | |
|when | |
| $event:StartEvent($host:host, $name:name) over window:length(1) | |
| $events:List(size>5) from accumulate ( | |
| $event2:StartEvent( | |
| this != $event, | |
| host == $host, | |
| name == $name, | |
| this before[0s, 10h] $event | |
| ); collectList($event2) | |
| ) | |
|then | |
| insert(new String("OK-"+$events.size())); | |
| //insert(new String("OK")); | |
|end | |
|""".stripMargin | |
val e = DroolsEngine(drl, DroolsEngineConfig.configWithIdentity) | |
e.fireAllRules() | |
e.strings should have size (0) | |
val event = """{"host":"zorglub", "name":"apache"}""" | |
val eventType = "test.StartEvent" | |
(1 to 10).foreach { _ => e.insertJson(event, eventType); e.advanceTimeHours(5) } | |
e.fireAllRules() | |
e.strings should have size (0) | |
(1 to 10).foreach { _ => e.insertJson(event, eventType); e.advanceTimeMinutes(10) } | |
e.fireAllRules() | |
e.strings should have size (1) | |
} | |
it should "be possible to access the session clock" in { | |
val drl = | |
"""package test //#EEX12 | |
|import org.kie.api.time.SessionClock; // Just for information | |
| | |
|rule "init" | |
|duration 10000 // 10 seconds | |
|when then | |
| insert("OK-"+drools.getWorkingMemory().getSessionClock().getCurrentTime()); | |
|end | |
| | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.fireAllRules() | |
engine.strings should have size (0) | |
engine.timeShiftInSeconds(12) | |
engine.fireAllRules() | |
engine.strings should have size (1) | |
} | |
it should "be possible to reason on object age (IN FACT NO)" ignore { | |
val drl = | |
"""package test //#EEX13 | |
| | |
|declare Event @role(event) @expires(48h) | |
| name:String | |
|end | |
| | |
|rule "when too old" | |
|when | |
| Event(now() - this.timestamp > 1000) // THERE'S NO now() or SessionClock defaults !!! | |
|then | |
| insert("OK") | |
|end | |
| | |
|rule "init" | |
|when then | |
| insert(new Event("event")); | |
|end | |
| | |
|""".stripMargin | |
info("https://stackoverflow.com/questions/34563843/drools-time-based-constraints-and-now") | |
info("""Drools does not have a continually updated notion of "now" in its absolute sense""") | |
val engine = DroolsEngine(drl) | |
engine.fireAllRules() | |
engine.strings should have size (0) | |
engine.timeShiftInSeconds(12) | |
engine.fireAllRules() | |
engine.strings should have size (1) | |
} | |
it should "be possible to reason on timeouts (naive implementation)" in { | |
val drl = | |
"""package test //#EEX14 | |
| | |
|import java.util.Date; | |
| | |
|declare TickClock | |
| @propertyReactive now : Date | |
|end | |
| | |
| | |
|rule "CreateTickClock" | |
|when | |
| not($tickClock:TickClock()) | |
|then | |
| TickClock tickTocker = new TickClock(); | |
| tickTocker.setNow(new Date(drools.getWorkingMemory().getSessionClock().getCurrentTime())); | |
| insert(tickTocker); | |
|end | |
| | |
| | |
|rule "UpdateTickClock" | |
|//timer (cron: 0/1 * * * * ? ) // every minute | |
|timer ( 1s 2s ) // every 2 seconds, with 1 second initial delay | |
|when | |
| $tickClock:TickClock() | |
|then | |
| modify($tickClock) { | |
| setNow(new Date(drools.getWorkingMemory().getSessionClock().getCurrentTime())); | |
| } | |
|end | |
| | |
|// --------------------------------------------------------------------------------- | |
| | |
|declare Something @role(event) | |
| peremptionDate: Date | |
|end | |
| | |
| | |
|rule "AlertOnLatency" | |
|when | |
| $tickClock:TickClock($now:now) | |
| $monitored:Something( peremptionDate < $now) | |
|then | |
| insert("OK"); | |
|end | |
| | |
|rule "init" | |
|when then | |
| insert(new Something(new Date(drools.getWorkingMemory().getSessionClock().getCurrentTime()+5000))); | |
|end | |
| | |
|""".stripMargin | |
info("inspired from https://stackoverflow.com/questions/34735507/drools-cep-current-date") | |
info("Always use session clock in KB, never use calls such as new Date() or System.currentTimeMillis()") | |
info( | |
"""BUT THIS IS A VERY BAD PRACTICE !! | |
| AS RULES RELYING ON TickClock WILL FIRE UP CONTINUALLY AT THE SAME frequency as | |
| for TickClock updates. To Avoid that you'll have to introduce supplementary facts | |
| to keep in memory that the rule has already been triggered !!! | |
| SEE NEXT Exemple for a quite better example based on window:time""" | |
) | |
val engine = DroolsEngine(drl) | |
engine.fireAllRules() | |
engine.strings should have size 0 | |
engine.timeShiftInSeconds(6) | |
engine.fireAllRules() | |
engine.strings should have size 1 | |
} | |
it should "be possible to reason on timeouts (a better implementation)" in { | |
val drl = | |
"""package test //#EEX15 | |
| | |
|declare MyEvent @role(event) @timestamp(timestamp) @duration(validityDelay) | |
| id: String @key | |
| timestamp: long | |
| validityDelay: long | |
|end | |
| | |
|rule "init" | |
|when then | |
| insert(new MyEvent("high temperature", 42000, 10000)); | |
|end | |
| | |
|rule "find expired events" | |
|when | |
| MyEvent($id:id) | |
| not MyEvent(id == $id) over window:time(20s) | |
|then | |
| insert("OK"); | |
|end | |
| | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.fireAllRules() | |
engine.strings should have size 0 | |
engine.timeShiftInSeconds(50) // t+50s | |
engine.fireAllRules() | |
engine.strings should have size 0 | |
engine.timeShiftInSeconds(20) // t+70s | |
engine.fireAllRules() | |
engine.strings should have size 1 | |
info( | |
"""Here in this way the chosen window time period is very important | |
| as it must coincide with the chosen timeout unfortunately as | |
| the event start is used to check if the event is over the | |
| window [-10s,now] frame window; thus the delay makes no sense | |
| unfortunately. | |
| Let's check the next example for the right approach also based on sliding windows""" | |
) | |
} | |
it should "be possible to reason on timeouts (the right implementation)" in { | |
val drl = | |
"""package test //#EEX16 | |
| | |
|global org.slf4j.Logger logger | |
| | |
|// @duration and @expires are just used to optimize memory management | |
|// in order to remove events after a given delay at (timestamp + validityDelay + 30s) | |
|declare MyEvent @role(event) @timestamp(timestamp) @duration(validityDelay) @expires(30s) | |
| id: String @key | |
| timestamp: long | |
| validityDelay: long | |
|end | |
| | |
|declare MyEventEOL @role(event) @timestamp(timestamp) | |
| id: String @key | |
| timestamp: long | |
|end | |
| | |
|rule "init" | |
|when then | |
| insert(new MyEvent("high temperature", 42000, 20000)); | |
|end | |
| | |
| | |
|rule "setup event EOL" | |
|when | |
| MyEvent($id:id, $timestamp:timestamp, $delay:validityDelay) | |
|then | |
| insertLogical(new MyEventEOL($id, $timestamp+$delay)); | |
|end | |
| | |
| | |
|rule "take a look into unexpired events" | |
|when | |
| MyEvent($id:id) | |
| $eol:MyEventEOL(id == $id) over window:time(5s) | |
|then | |
| logger.info("unexpired " + $eol + " triggered @ "+drools.getWorkingMemory().getSessionClock().getCurrentTime()); | |
|end | |
| | |
| | |
|rule "find expired events" | |
|when | |
| MyEvent($id:id) | |
| not MyEventEOL(id == $id) over window:time(5s) | |
|then | |
| insertLogical("OK"); | |
|end | |
| | |
|""".stripMargin | |
val engine = DroolsEngine(drl) | |
engine.fireAllRules() | |
engine.strings should have size 0 | |
engine.timeShiftInSeconds(50) // t+50s | |
engine.fireAllRules() | |
engine.strings should have size 0 | |
engine.timeShiftInSeconds(15) // t+65s EOL=62 but window:time=[60, 65] as (window:time(5s)) | |
engine.fireAllRules() | |
engine.strings should have size 0 | |
engine.timeShiftInSeconds(5) // t+70s EOL=62 window:time=[65,70] as (window:time(5s)) | |
engine.fireAllRules() | |
engine.strings should have size 1 | |
engine.timeShiftInSeconds(22) // t+92s 42s (start) + 20s (validityDelay) + 30 (expired) | |
engine.fireAllRules() | |
engine.strings should have size 1 | |
engine.timeShiftInSeconds(1) // t+93s MyEvent has expired, memory automaticall cleaned up | |
engine.fireAllRules() | |
engine.strings should have size 0 | |
info( | |
"""It works perfectly, it worth to be noted that window:time(5s) | |
|take into account in fact [cur-5, cur] but also [cur, +oo] which means it take | |
|all future events""" | |
) | |
} | |
it should "expires automatically any events if they can't be involved anymore in any activations" in { | |
info("By default, an event expires when the event can no longer match and activate any of the current rules.") | |
info("@expires => overrides the implicit expiration offset calculated from temporal constraints and sliding windows in the KIE base.") | |
info("unless you use soft expiration : @expires( value = \"48h\", policy = TIME_SOFT )") | |
} | |
} | |
org.scalatest.tools.Runner.main(Array("-oDF", "-s", classOf[UnderstandingEventsRules].getName)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment