clara

http://www.clara-rules.org/
2020-01-15T12:23:03.156Z

Hi. Im evaluating potentially using Clara for one of our use cases. Can you imagine a sensible way to be able to determine the start-time and end-time of a given rule execution? Say I have a rule that has some expensive operation… I’d like to be able to record when it started and ended, ideally without adding the required code to the rule itself and adding the values to the emitted fact (as it would be great if all rules exposed this information implicitly). I’ve started looked into: • The inspect + trace tools dont seem to provide it • Creating a new Listener, but this may be a bad idea given it would no longer be immutable if its calling out for the current time? • Creating a macro and extend the parsing logic to wrap the rule execution somehow Any thoughts / suggestions appreciated. Many thanks

2020-01-15T17:10:01.157Z

@mbragg I don’t think it’s as straightforward as perhaps it seems. Rules aren’t the same as something like a fn body that you can time

2020-01-15T17:10:34.157800Z

The rules are structures that are used to create a rete based data flow tree (forest really)

2020-01-15T17:12:40.160800Z

Some examples of the complexity: (1) In some cases LHS conditions from separate rules may end up being shared as one common node in this tree. (2) rule conditions are nodes on the tree. They have varying degrees of eager vs lazy behavior. In most cases, the engine tries to batch facts up prior to evaluation of conditions

2020-01-15T17:13:41.162200Z

(3) before fire-rules happens a certain degree of evaluation takes place. Once fire-rules happens more work is done but always oriented towards batched fact propagation.

2020-01-15T17:14:46.163700Z

The inspection/trace stuff can be useful to get a sense of how many times a condition is being evaluated/matched. This tends to just give a relative “count” to compare and often find the outliers.

2020-01-15T17:16:01.165400Z

But overall. The rules are more like “sql query structures” where they are then compiled to a network, like the “db eval plan”

2020-01-15T17:16:23.166100Z

So the concerns tend to be things like how many times is this “join condition” being evaluated given some facts.

2020-01-15T17:19:11.169200Z

With profiling you can find actually hot spot functions which sometimes end up being a fn used in a condition that is being evaluated a large number of times:

[MyFact (my-fn ...)]
If that condition has a large number of matches you may profile and see my-fn is a hot spot and looking to be a perf issue.

👍 1
2020-01-15T17:46:25.171900Z

Thanks @mikerod for the explanation, makes sense. I guess I was specifically meaning everything in the RHS do block for a given rule. But I can see it may not be that straight forward... especially if there’s batching involved.

2020-01-16T16:08:23.201800Z

Thanks @wparker

2020-01-15T17:49:11.172100Z

@mbragg the RHS is easier to time at least

2020-01-15T17:49:30.172600Z

I mean you can wrap that fairly easily - but still the time it takes to do the insert! sort of functions is where the batching happens

2020-01-15T17:49:59.173200Z

so you if you’re RHS is:

(insert! (->Something ?x))
timing it won’t necessarily tell you much

2020-01-15T17:50:31.173800Z

that same RHS may end up being activated N times - so it evaluates N times, and you have N insert! calls all batched, so no fact is actually inserted right away until a later cycle

2020-01-15T17:51:05.174400Z

the batching is very useful from a perf standpoint too - but does complicate timing things

2020-01-15T18:42:42.177800Z

@mikerod ok understood. It think I’m going to suggest that this “rule timing” requirement is something we drop if we go with Clara, as it sounds like it doesn’t really make sense... in the way they are imagining rules being executed anyway. Thanks for all your help

Matthew Pettis 2020-01-15T18:47:54.179Z

In a defrule RHS, when making a record to insert, is there a way to access the rule name (the symbol associated with the rule that is firing) to store as a field in the inserted fact?

2020-01-15T18:52:26.179800Z

@matthew.pettis I suppose you want this instead of being repetitive?

(defrule my-rule
=>
(insert! (->MyFact `my-rule))

Matthew Pettis 2020-01-15T18:55:00.181700Z

Yeah, right now I'm copying the name as the rule as a string in the creation of MyFact , so if there is a way to resolve the name of the rule in which the insert is operating that's what I'm looking for (so, as you said, I'm not repetitive in the sense of mis-copypasting the rule name into the string...)

Matthew Pettis 2020-01-15T18:57:01.181800Z

Thinking of something like this, so I can use the same insert statement and not have to copy in the rulename somehow...

2020-01-15T19:05:46.182400Z

@matthew.pettis have to look to think if anything is directly available

2020-01-15T19:05:52.182700Z

however, you can always opt to make your own macro

2020-01-15T19:06:35.184200Z

defrule itself is just a macro and rules and queries in Clara are all modeled in a data-oriented data structure underneath that alternative DSL’s can be written to produce (via macros typically)

Matthew Pettis 2020-01-15T19:06:37.184300Z

OK. A macro implementation might be in line for me too.

2020-01-15T19:06:44.184600Z

or you can have a macro that expands to defrule for your case

Matthew Pettis 2020-01-15T19:07:53.186300Z

I think I may follow the custom macro expansion to defrule, thanks. Was just wondering if it was a common need and something was built in that I was missing...

2020-01-15T19:08:10.186700Z

an old (and probably no longer able to run examples) post, that did give a good overview of alterantive DSL’s http://www.toomuchcode.org/blog/2015/11/14/insta-declarative-dsls/ just for reference if that concept is something you find further interest in - you don’t really need it for this small case

👍 1
2020-01-15T19:11:02.187200Z

I don’t see anything built-in as of now

2020-01-15T19:12:51.187900Z

I have came across a similar use-case in the past. I think I just repeated the name basically - I believe I used keywordized version instead though

2020-01-15T19:12:58.188200Z

but can be error-prone indeed

2020-01-15T19:14:01.189500Z

I’ve also done alternative DSLs that compile to underlying rule structures though in some cases where there was more to it

Matthew Pettis 2020-01-15T19:16:49.192Z

I like the macro wrapper for the cases where I want to insert a fact tracking what rule was fired. And, yeah, in my use case, I was thinking of using rule firing to track all of the rules that were fired on a given fact. This is like using rules as a way to track all of the ways a fact can violate rules without making a big conditional statement that checks all conditions and logs that observation.

2020-01-15T19:24:47.192200Z

Yeah that makes sense

2020-01-15T22:42:57.193Z

In case a concrete demonstration helps for clarity, the rule RHS calls like insert! add things to a cache that will be processed later (the details of that probably aren’t super important here), the default implementation of which is in this file: https://github.com/cerner/clara-rules/blob/master/src/main/clojure/clara/rules/update_cache/core.cljc It’s certainly possible to have a poorly-performing function called in a rule, for which profiling with something like VisualVM can be useful, but a simple timer isn’t necessarily much help with diagnosing poorly performing rules networks as Mike has elaborated on.