I have some difficulties to do Drools run in pseudo clock.
I configure my engine in stream Mode, and choose the kind of clock I use thru realMode property.
Date refDate = new Date(System.currentTimeMillis());
boolean realMode = false;
SessionPseudoClock clock = null;
KieServices ks = KieServices.Factory.get();
KieSessionConfiguration config = KieServices.Factory.get().newKieSessionConfiguration();
if(realMode) {
config.setOption( ClockTypeOption.get("realtime") );
} else {
config.setOption( ClockTypeOption.get("pseudo") );
}
KieContainer kc = ks.getKieClasspathContainer();
KieSession ksession = kc.newKieSession("cep-rules",config);
KieRuntimeLogger logger = ks.getLoggers().newFileLogger( ksession, "./out/helloworld" );
addNewLogLine (ksession, "GDE" , 0);
addNewLogLine (ksession, "GDE" , 11);
addNewLogLine (ksession, "GDE" , 3);
addNewLogLine (ksession, "GDE" , 8);
logger.close();
ksession.dispose();
The code of AddNewLogLine Function
if( realMode) {
try {
Thread.sleep(delaiInSeconds*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
if (clock==null) {
clock = ksession.getSessionClock();
}
clock.advanceTime( delaiInSeconds, TimeUnit.SECONDS );
}
delai += delaiInSeconds;
LogItem logLine = new LogItem();
logLine.setEventDate(new Date (refDate.getTime()+(delai*1000)));
logLine.setMessage("Message number " + count);
logLine.setSourceSystemName(sourceSystemName);
System.out.println(logLine);
ksession.insert( logLine );
ksession.fireAllRules();
I leave appart some extra code that is not relevant for my problema.
The rule :
declare LogItem
#role (event)
#timestamp( eventDate )
end
rule "LogInserted"
dialect "mvel"
when
l : LogItem ( )
then
System.out.println ("New Log inside from " + l.getSourceSystemName() );
end
rule "Nb Log SameSystem"
dialect "mvel"
when
accumulate( LogItem ( sourceSystemName == "GDE") over window:time(10s) ; $cnt: count(1); $cnt == 2 )
then
System.out.println ("2 Logs in engine" );
end
The objective : Detect two logs line in a window of 10 seconds.
The first and seconds are not (11s) and the 2 following lines, yes.
In real mode it works fine. Here is the result :
LogItem { message : Message number 1, date : 28/03/2017 11:17:26, sourceSystemName : GDE }
New Log inside from GDE
LogItem { message : Message number 2, date : 28/03/2017 11:17:37, sourceSystemName : GDE }
New Log inside from GDE
LogItem { message : Message number 3, date : 28/03/2017 11:17:40, sourceSystemName : GDE }
New Log inside from GDE
2 Logs in engine
LogItem { message : Message number 4, date : 28/03/2017 11:17:48, sourceSystemName : GDE }
New Log inside from GDE
2 Logs in engine
In pseudo clock mode it doesn't work. Because the 4 lines are inserted in the same time, so the 2 first activate the rule. So the Pseudo clock isn't used.
Here is the result :
LogItem { message : Message number 1, date : 28/03/2017 11:17:26, sourceSystemName : GDE }
New Log inside from GDE
LogItem { message : Message number 2, date : 28/03/2017 11:17:37, sourceSystemName : GDE }
New Log inside from GDE
2 Logs in engine
LogItem { message : Message number 3, date : 28/03/2017 11:17:40, sourceSystemName : GDE }
New Log inside from GDE
LogItem { message : Message number 4, date : 28/03/2017 11:17:48, sourceSystemName : GDE }
New Log inside from GDE
I suppose it's because I don't manage de pseudo clock. But I'm not able to find where I'm wrong.
Anybody ?
Thanks in advance;
I think your problem is due to incorrect manipulation of the pseudo-clock. Also, for best results, run the session in a thread of its own which executes fireUntilHalt:
Runnable fireUntilHaltRunnable = new Runnable() {
public void run() {
// System.out.println( "fireUntilHalt" );
kSession.fireUntilHalt()
}
};
Thread fireUntilHaltThread = new Thread(fireUntilHaltRunnable);
Facts are inserted from another thread. If you are running tests with the pseudo-clock, make sure the pseudo-clock is set to the timestamp of the event if you are using events that contain the timestamp as a field:
private void advance( Date eventDate ){
long currentTime = clock.getCurrentTime();
long eventTime = eventDate.getTime();
clock.advanceTime( eventTime - currentTime, TimeUnit.MILLISECONDS );
}
Do this before you insert the next fact. There is no need to sleep between inserts.
Related
Good day everyone,
Currently I'm working with a simple temperature sensor that basically updates 4 times a second on the current temperature, and filter using the following rule in Drools:
rule "temperature detected"
when
$acc: Number ( doubleValue > 22.0 ) from accumulate(
$sensorMessage: SensorMessage($topic: topic, $timeStamp: timestamp, $data: data) over window:time ( 10s ) from entry-point "temperature_sensor",
average ( $sensorMessage.getDouble("temperature", -100) )
)
then
logger.info("Received temperature data > 22.0 -> " + $acc);
end
This, however, logs the console after EVERY sensor update as long as the accumulated temperature average is larger than 22, over a window of 10 seconds.
This of course, is far from ideal.
Would it be possible to, after a sensor update has been received, continue listening UNTIL no more updates are received for, say, 3 seconds. Then log the starting time of when a sensor update first is detected, and the ending time. And only log these two timestamps if at least 10 updates have been received altogether, and some criterium is met.
Example scenarios (T being some target temperature):
If a motion sensor sends 20 updates within 2 seconds, the time of the first update and time of the last update is logged.
If a motion sensor sends 6 updates in 1 second, then another 6 updates after 5 seconds, nothing happens, as we expect more motion sensor updates to properly classify it as motion.
If a temperature sensor sends 10 updates within 1 second, and the average of all 10 pings is <= T, nothing happens, however, if it does exceed T, we log a single temperature "alert".
This is what I came-up with after few hours. Some requirements are not clear for me, like what is 'the last update is logged', each one is 'the last'. Following may still require a work to do, but you may get general idea: we have single object in the session we update and monitor.
import java.lang.Double;
import java.util.ArrayList;
declare SensorMessage
#role(event)
#timestamp(timestamp)
#expires(10s)
end
rule "temperature monitoring"
when
$meta: SensorMetadata()
$acc: Double () from accumulate(
$message: SensorMessage() over window:time(1s),
average ($message.getTemperature())
)
then
$meta.setTemperature($acc);
update($meta);
end
rule "temperature activated"
when
$meta: SensorMetadata(highTemperature == false, temperature >= $meta.targetTemperature)
$lastMessage: SensorMessage($lastTimestamp: timestamp)
not (SensorMessage(timestamp > $lastMessage.timestamp))
then
$meta.setLastActivated($lastMessage.getTimestamp());
$meta.setHighTemperature(true);
update($meta);
System.out.printf("temperature activated %.2f%n", $meta.getTemperature());
end
rule "temperature deactivated"
when
$meta: SensorMetadata(highTemperature == true, temperature < $meta.targetTemperature)
$lastMessage: SensorMessage($lastTimestamp: timestamp)
not (SensorMessage(timestamp > $lastMessage.timestamp))
then
$meta.setHighTemperature(false);
update($meta);
System.out.printf("temperature deactivated %.2f%n", $meta.getTemperature());
end
rule "throttle state activated"
when
$meta: SensorMetadata(throttleState == false)
$lastMessage: SensorMessage($lastTimestamp: timestamp)
not (SensorMessage(timestamp > $lastMessage.timestamp))
$messages: ArrayList(size > 20) from collect(
$message: SensorMessage() over window:time(1s)
)
then
$meta.setLastThrottled($lastMessage.getTimestamp());
$meta.setThrottleState(true);
update($meta);
System.out.printf("throttle state activated %d%n", $messages.size());
end
rule "throttle state deactivated"
when
$meta: SensorMetadata(throttleState == true)
$lastMessage: SensorMessage($lastTimestamp: timestamp)
not (SensorMessage(timestamp > $lastMessage.timestamp))
$messages: ArrayList(size <= 20) from collect(
$message: SensorMessage() over window:time(1s)
)
then
$meta.setThrottleState(false);
update($meta);
System.out.printf("throttle state deactivated %d%n", $messages.size());
end
the test
#DroolsSession(resources = "file:src/main/resources/draft/rule.drl", showStateTransitionPopup = true, ignoreRules = "* monitoring")
public class PlaygroundTest extends DroolsAssert {
#RegisterExtension
public DroolsAssert droolsAssert = this;
#Test
#TestRules(ignore = "throttle *", expectedCount = {
"2", "temperature activated",
"2", "temperature deactivated" })
public void testTemperatureActivationDeactivation() throws IOException {
insert(new SensorMetadata(22));
Date date = new Date(0);
for (int i = 0; i < 100; i++) {
double temperature = i == 80 ? 100 : i == 81 ? -100 : i < 50 ? i : 1 / i;
insertAndFire(new SensorMessage(date, temperature));
date = addMilliseconds(date, 1);
advanceTime(MILLISECONDS, 1);
}
advanceTime(10, SECONDS);
assertFactsCount(1);
}
#Test
#TestRules(ignore = "temperature *", expectedCount = {
"2", "throttle state activated",
"2", "throttle state deactivated" })
public void testThrottleMode() throws IOException {
insert(new SensorMetadata(22));
Date date = new Date(0);
for (int i = 0; i < 100; i++) {
insertAndFire(new SensorMessage(date, 22));
int advanceTime = i * 3;
date = addMilliseconds(date, advanceTime);
advanceTime(MILLISECONDS, advanceTime);
}
advanceTime(10, SECONDS);
assertFactsCount(1);
}
}
and models
public class SensorMessage {
private Date timestamp;
private double temperature;
public SensorMessage(Date timestamp, double temperature) {
this.timestamp = timestamp;
this.temperature = temperature;
}
// getters
}
public class SensorMetadata {
private volatile Date lastActivated;
private volatile Date lastThrottled;
private double temperature;
private boolean highTemperature;
private boolean throttleState;
private double targetTemperature;
public SensorMetadata(double targetTemperature) {
this.targetTemperature = targetTemperature;
}
// getters and setters
}
I am trying to set KieSession date to same date with the date variable in my object before running the rules. I use this configuration to create my KieSession
KieSessionConfiguration configuration = KieServices.Factory.get().newKieSessionConfiguration();
configuration.setOption(ClockTypeOption.get("pseudo"));
Before running the rules I use advanceTime() to set the date of the session to desired date.
final SessionPseudoClock clock = kSession.getSessionClock();
clock.advanceTime(object.getDate().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
final List<Command<?>> commands = new ArrayList<>();
commands.add(CommandFactory.newInsertElements(objects)
commands.add(CommandFactory.newInsert(object, "object"));
final ExecutionResults results = kSession.execute(CommandFactory.newBatchExecution(commands));
This resulted misfires in the rules that uses sliding windows. Lets say checking the objects that passed in 1 hour and I don't have any in the last hour. I only have 3 objects day before. Here is an example dataset of objects.
objects: [
{
clientId: "id",
date: 2021-02-09T12:00:38.249Z,
...
}
{
clientId: "id",
date: 2021-02-09T13:00:38.249Z,
...
}
{
clientId: "id",
date: 2021-02-09T14:00:38.249Z,
...
}
]
I have a rule which checks if there are more than 2 objects with the same clientId over 1 hour.
$object : Object($clientId : clientId)
List( size > 2 ) from collect ( Object( this before $object, clientId == $clientId ) over window:time(1h))
When I pass an object with these values. The rule above returns true but we clearly don't have any objects that has a date within last hour.
{ clientId: "id", date: 2021-02-10T14:00:38.249Z, ... }
I believe this is broken because of the new configuration as it was working previously (when I did not try to change session clock) but I want session date to be equal to object date. Anyone has ideas what is the problem here and how to fix it?
As Roddy of the Frozen Peas made the remark, you need to manipulate SessionPseudoClock in between every insert of your events. I'd implement a new Command, extend InsertElementsCommand and #Override it's execute method:
#Override
public Collection<FactHandle> execute(Context context) {
KieSession kSession = ((RegistryContext) context).lookup(KieSession.class);
List<FactHandle> handles = new ArrayList<>();
Object workingMemory = StringUtils.isEmpty(super.getEntryPoint()) ?
kSession :
kSession.getEntryPoint(super.getEntryPoint());
SessionPseudoClock clock = kSession.getSessionClock();
super.objects.forEach(event -> {
clock.advanceTime(((YourObject) event).getDate().getTime() - clock.getCurrentTime(), TimeUnit.MILLISECONDS);
handles.add(((EntryPoint) workingMemory).insert(event));
});
...
return handles;
}
and instead of:
commands.add(CommandFactory.newInsertElements(objects));
I'd:
commands.add(new YourInsertElementsCommand(objects));
I am trying to count the number of objects by using QueryResults. My rule is:
query "Test"
m : Message()
end
function int countFacts(String queryString) {
QueryResults queryResults = DroolsTest.getQueryResults(queryString);
if (queryResults != null) {
System.out.println("Total FACTS found: " + queryResults.size());
return queryResults.size();
}
return 0;
}
rule "Hello World"
when
m : Message( status == Message.HELLO, myMessage : message )
eval(countFacts("Test")>0 )
then
System.out.println( myMessage );
end
And in the java side
public static QueryResults getQueryResults(String queryName) {
System.out.println("inside queryResults for queryName " +queryName );
QueryResults queryResults = kSession.getQueryResults(queryName);
return queryResults;
}
When I am trying to run the rule, execution stops and nothing happens.
The kSession.getQueryResults(queryName) returns nothing and after some time I have to manually terminate the execution.
what is wrong here?
I think the issue here is your thread being blocked. If your requirement is to count the number of facts in your session as part of a rule, then you can do it in a much more "Drools-friendly" way using an accumulate:
rule "More than 2 HELLO messages"
when
Number(longValue > 2) from accumulate (
Message( status == Message.HELLO),
count(1)
)
then
System.out.println( myMessage );
end
If you really want to call a query from the when part of a rule, you will need to pass an unbound variable to the query in order to get the result back in your rule:
query countMessages(long $n)
Number($n:= longValue) from accumulate (
Message(),
count(1)
)
end
rule "Hello World"
when
m : Message( status == Message.HELLO, myMessage : message )
countMessages($n;)
eval($n > 0)
then
System.out.println( m );
end
Hope it helps,
I tried to insert a value into a Hashmap() but my test case fails when I tried to put the result of an accumulate function($totals in the drl file) as the value of the Hashmap. I've also tried Integer.valueOf($totals) instead of $totals without luck.
My drl file contains:
rule "calculate total per item"
no-loop
when
$o : Order( $lines : orderLines)
$ol : OrderLine(this memberOf $lines.values() , quantity > 0)
$totals : Number(intValue > 0) from accumulate (
OrderLine($qty : quantity, item.barcode == $ol.getItem().getBarcode()) from $lines.values(),
sum($qty)
)
then
System.out.println("total: " + $totals); //prints out total: 7.0
modify($o){
getPerItems().put($ol.getItem().getBarcode(), $totals); //this line throws an error
//getPerItems().put($ol.getItem().getBarcode(), 1 ); // If I replace $totals with a number, it works
}
end
My Order class
public class Order {
private HashMap<Integer, OrderLine> orderLines = new HashMap<Integer, OrderLine>();
private HashMap<String, Integer> perItems = new HashMap<String, Integer>();
...
public HashMap<String, Integer> getPerItems() {
return perItems;
}
}
Any advice to overcome this issue?
Try this binding:
Number($totals : intValue > 0)
It would help if you add the error message verbatim to your question.
"Throws an error" - does it mean "throws an exception" or "reports an error". When and where ...?
I am quite new to drools.
I am working on an application where my drools engine will get a series of event every second. I need to see if all the events in last 10 seconds has attribute value below 10, if the condition is true, I have to do some processing. Here is the example code which I tried, Please help me understand and resolve the issue.
My Rule file.....
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
declare Employee
#role (event)
#expires(10s)
end
// Using timer to ensure rule processing starts only after 10 secs,
//else processing starts as soon as first event comes in
rule "Test Timer"
no-loop true
10timer(int: 5s)
when
$E : Employee()
$total : Number( doubleValue < 1 )
from accumulate( Employee( Age > 10 ), count() )
then
System.out.println( $E.getName() + " is crossing the threshold of 20");
retract($E);
end
And Main class
// import classes removed from here...
public class MainClass {
/**
* #param args
*/
public static void main(String[] args){
//Create KnowledgeBase...
KnowledgeBase knowledgeBase = createKnowledgeBase();
//Create a stateful session
StatefulKnowledgeSession session = knowledgeBase.newStatefulKnowledgeSession();
// KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newConsoleLogger(session);
try {
// Using random generator to simulate the data.
int randomInt=0;
Random randomGenerator = new Random();
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = null;
while (true) {
Thread.sleep(1000);
date = new Date();
randomInt = randomGenerator.nextInt(12);
//Create Facts and insert them
Employee emp = new Employee();
emp.setName("Anurag" + randomInt);
emp.setAge(randomInt);
//LOAD THE FACT AND FIREEEEEEEEEEEEEEEEEEE............
System.out.println(dateFormat.format(date)+ " => Random no " + randomInt);
session.insert(emp);
session.fireAllRules();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
session.dispose();
}
}
/**
* Create new knowledge base
*/
private static KnowledgeBase createKnowledgeBase() {
KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
//Add drl file into builder
File drl = new File("D:\\eclipse\\worspace\\Research\\misc\\testforall.drl");
builder.add(ResourceFactory.newFileResource(drl), ResourceType.DRL);
if (builder.hasErrors()) {
System.out.println(builder.getErrors().toString());
//throw new RuntimeException(builder.getErrors().toString());
}
KnowledgeBase knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
//Add to Knowledge Base packages from the builder which are actually the rules from the drl file.
knowledgeBase.addKnowledgePackages(builder.getKnowledgePackages());
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( EventProcessingOption.STREAM );
return knowledgeBase;
}
}
public class Employee {
private String Name;
private int Age;
// getter - setters
}
did you check the Drools Fusion documentation?
First of all, Employee doesn't sounds as a good idea for an Event. Events are meaningful changes of states of something related with your domain. So, an event could be the time of arrival of an Employee to the office, or the time of departure, but the Employee itself is a domain entity (or a fact for the rule engine) more than an event.
If you are interested in using Drools fusion temporal operators I recommend you to read about sliding windows (temporal ones) which will allow you to see what happen in the last ten seconds all the time. You don't need to use timers for that.
Cheers
You forgot telling what happened when you ran it, if you did.
If your entities set is not very large, I think this problem can be solved very easily with the base Drools distribution.
I have a similar app to yours and works for me:
rule "Clear only auxiliar fact"
salience 1
when
af: AuxFact()
then
DroolsRepository.retractFact(af);
end
rule "Clear auxiliar fact and an old meaningful fact"
salience 1000
when
af: AuxFact()
mf: MeaningfulFact()
then
if(DroolsRepository.getCurrentTimeMillis() - tmf.getCreationDate().getTime() > 5000){
DroolsRepository.retractFact(af);
DroolsRepository.retractFact(mf);
// YOUR MEANINGFUL CODE
}
else{
DroolsRepository.retractFact(af);}
end
query "getAllFacts"
$result: Fact()
end
and
// Boot rules re-executing thread.
new Thread(new Runnable(){
public void run(){
do{
kSession.insert(new AuxFact());
try{
Thread.sleep(99);}
catch(InterruptedException e){
e.printStackTrace();}}
while(true);}
}).start();
A similar approach could be simpler and effective.