Event patterns match when an event or multiple events occur that match the pattern's definition. Patterns can also be time-based.
Pattern expressions can consist of filter expressions combined with pattern operators. Expressions can contain further nested pattern expressions by including the nested expression(s) in () round brackets.
There are 5 types of operators:
Operators that control pattern sub-expression repetition: every
Logical operators: and, or, not
Temporal operators that operate on event order: -> (followed-by)
Guards are where-conditions that filter out events and cause termination of the pattern finder. Examples are timer:within.
Observers observe time events as well as other events. Examples are timer:interval and timer:at.
This is an example pattern expression that matches on every ServiceMeasurement events in which the value of the latency event property is over 20 seconds, and on every ServiceMeasurement event in which the success property is false. Either one or the other condition must be true for this pattern to match.
every (spike=ServiceMeasurement(latency>20000) or error=ServiceMeasurement(success=false))
In the example above, the pattern expression starts with an every operator to indicate that the pattern should fire for every matching events and not just the first matching event. Within the every operator in round brackets is a nested pattern expression using the or operator. The left hand of the or operator is a filter expression that filters for events with a high latency value. The right hand of the operator contains a filter expression that filters for events with error status. Filter expressions are explained in Section 7.3, “Pattern Filter Expressions”.
The example above assigned the tags spike and error to the events in the pattern. The tags are important since the engine only places tagged events into the output event(s) that a pattern generates, and that the engine supplies to listeners of the pattern statement. The tags can further be selected in the select-clause of an EQL statement as discussed in Section 6.4.2, “Pattern-based event streams”.
Pattern statements are created via the EPAdministrator interface. The EPAdministrator interface allows to create pattern statements in two ways: Pattern statements that want to make use of the EQL select clause or any other EQL constructs use the createEQL method to create a statement that specifies one or more pattern expressions. EQL statements that use patterns are described in more detail in Section 6.4.2, “Pattern-based event streams”. Use the syntax as shown in below example.
EPAdministrator admin = EPServiceProviderManager.getDefaultProvider().getEPAdministrator(); String eventName = ServiceMeasurement.class.getName(); EPStatement myTrigger = admin.createEQL("select * from pattern [" + "every (spike=" + eventName + "(latency>20000) or error=" + eventName + "(success=false))]");
Pattern statements that do not need to make use of the EQL select clause or any other EQL constructs can use the createPattern method, as in below example.
EPStatement myTrigger = admin.createPattern( "every (spike=" + eventName + "(latency>20000) or error=" + eventName + "(success=false))");
When a pattern fires it publishes one or more events to any listeners to the pattern statement. The listener interface is the net.esper.client.UpdateListener interface.
The example below shows an anonymous implementation of the net.esper.client.UpdateListener interface. We add the anonymous listener implementation to the myPattern statement created earlier. The listener code simply extracts the underlying event class.
myPattern.addListener(new UpdateListener() { public void update(EventBean[] newEvents, EventBean[] oldEvents) { ServiceMeasurement spike = (ServiceMeasurement) newEvents[0].get("spike"); ServiceMeasurement error = (ServiceMeasurement) newEvents[0].get("error"); ... // either spike or error can be null, depending on which occurred ... // add more logic here } });
Listeners receive an array of EventBean instances in the newEvents parameter. There is one EventBean instance passed to the listener for each combination of events that matches the pattern expression. At least one EventBean instance is always passed to the listener.
The properties of each EventBean instance contain the underlying events that caused the pattern to fire, if events have been named in the filter expression via the name=eventType syntax. The property name is thus the name supplied in the pattern expression, while the property type is the type of the underlying class, in this example ServiceMeasurement.
Data can also be pulled from pattern statements via the iterator() method. If the pattern had fired at least once, then the iterator returns the last event for which it fired. The hasNext() method can be used to determine if the pattern had fired.
if (myPattern.iterator().hasNext()) { ServiceMeasurement event = (ServiceMeasurement) view.iterator().next().get("alert"); ... // some more code here to process the event } else { ... // no matching events at this time }
The simplest form of filter is a filter for events of a given type without any conditions on the event property values. This filter matches any event of that type regardless of the event's properties. The example below is such a filter. Note that this event pattern would stop firing as soon as the first RfidEvent is encountered.
com.mypackage.myevents.RfidEvent
To make the event pattern fire for every RfidEvent and not just the first event, use the every keyword.
every com.mypackage.myevents.RfidEvent
The example above specifies the fully-qualified Java class name as the event type. Via configuration, the event pattern above can be simplified by using the alias that has been defined for the event type.
every RfidEvent
Interfaces and superclasses are also supported as event types. In the below example IRfidReadable is an interface class, and the statement matches any event that implements this interface:
every org.myorg.rfid.IRfidReadable
The filtering criteria to filter for events with certain event property values are placed within parenthesis after the event type name:
RfidEvent(category="Perishable")
All expressions can be used in filters, including static method invocations that return a boolean value:
RfidEvent(MyRFIDLib.isInRange(x, y) or (x<0 and y < 0))
Filter expressions can be separated via a single comma ','. The comma represents a logical AND between expressions:
RfidEvent(zone=1, category=10) ...is equivalent to... RfidEvent(zone=1 and category=10)
The following set of operators are highly optimized through indexing and are the preferred means of filtering high-volume event streams:
equals =
not equals !=
comparison operators < , > , >=, <=
ranges
use the between keyword for a closed range where both endpoints are included
use the in keyword and round () or square brackets [] to control how endpoints are included
for inverted ranges use the not keyword and the between or in keywords
list-of-values checks using the in keyword or the not in keywords followed by a comma-separated list of values
At compile time as well as at run time, the engine scans new filter expressions for sub-expressions that can be indexed. Indexing filter values to match event properties of incoming events enables the engine to match incoming events faster. The above list of operators represents the set of operators that the engine can best convert into indexes. The use of comma or logical and in filter expressions does not impact optimizations by the engine.
For more information on filters please see Section 6.4.1, “Filter-based event streams”.
Filter criteria can also refer to events matching prior named events in the same expression. Below pattern is an example in which the pattern matches once for every RfidEvent that is preceded by an RfidEvent with the same asset id.
every A=RfidEvent -> B=RfidEvent(assetId=A.assetId)
The syntax shown above allows filter criteria to reference prior results by specifying the event name tag of the prior event, and the event property name. This syntax can be used in all filter operators or expressions including ranges and the in set-of-values check:
every A=RfidEvent -> B=RfidEvent(MyLib.isInRadius(A.x, A.y, x, y) and zone in (1, A.zone))
The every operator indicates that the pattern sub-expression should restart when the sub-expression qualified by the every keyword evaluates to true or false. Without the every operator the pattern sub-expression stops when the pattern sub-expression evaluates to true or false.
Thus the every operator works like a factory for the pattern sub-expression contained within. When the pattern sub-expression within it fires and thus quits checking for events, the every causes the start of a new pattern sub-expression listening for more occurances of the same event or set of events.
Every time a pattern sub-expression within an every operator turns true the engine starts a new active sub-expression looking for more event(s) or timing conditions that match the pattern sub-expression. If the every operator is not specified for a sub-expression, the sub-expression stops after the first match was found.
This pattern fires when encountering event A and then stops looking.
A
This pattern keeps firing when encountering event A, and doesn't stop looking.
every A
Let's consider an example event sequence as follows.
A1 B1 C1 B2 A2 D1 A3 B3 E1 A4 F1 B4
Table 7.1. 'Every' operator examples
Example | Description |
---|---|
every ( A -> B ) | Detect event A followed by event B. At the time when B occurs the pattern matches, then the pattern matcher restarts and looks for event A again.
|
every A -> B | The pattern fires for every event A followed by an event B.
|
A -> every B | The pattern fires for an event A followed by every event B.
|
every A -> every B | The pattern fires for every event A followed by every event B.
|
The examples show that it is possible that a pattern fires for multiple combinations of events that match a pattern expression. Each combination is posted as an EventBean instance to the update method in the UpdateListener implementation.
Let's consider the every operator in conjunction with a sub-expression that matches 3 events that follow each other:
every (A -> B -> C)
The pattern first looks for event A. When event A arrives, it looks for event B. After event B arrives, the pattern looks for event C. Finally when event C arrives the pattern fires. The engine then starts looking for event A again.
Assume that between event B and event C a second event A2 arrives. The pattern would ignore the A2 entirely since it's then looking for event C. As observed in the prior example, the every operator restarts the sub-expression A -> B -> C only when the sub-expression fires.
In the next statement the every operator applies only to the A event, not the whole sub-expression:
every A -> B -> C
This pattern now matches for any event A that is followed by an event B and then event C, regardless of when the event A arrives. Oftentimes this can be practical in combination with the and not syntax and the timer:within syntax as the next example shows.
This example looks at temperature sensor events named Sample. The pattern detects when 3 sensor events indicate a temperature of more then 50 degrees uninterrupted within 90 seconds of the first event, considering events for the same sensor only.
every sample=Sample(temp > 50) -> ( (Sample(sensor=sample.sensor, temp > 50) and not Sample(sensor=sample.sensor, temp <= 50)) -> (Sample(sensor=sample.sensor, temp > 50) and not Sample(sensor=sample.sensor, temp <= 50)) ) where timer:within(90 seconds))
The pattern starts a new sub-expression in the round braces after the first followed-by operator for each time a sensor indicated more then 50 degrees. Each sub-expression then lives a maximum of 90 seconds. Each sub-expression ends if a temperature of 50 degress or less is encountered for the same sensor. Only if 3 temperature events in a row indicate more then 50 degrees, and within 90 seconds of the first event, and for the same sensor, does this pattern fire.
Similar to the Java && operator the and operator requires both nested pattern expressions to turn true before the whole expression turns true (a join pattern).
Pattern matches when both event A and event B are found.
A and B
Pattern matches on any sequence A followed by B and C followed by D, or C followed by D and A followed by B
(A -> B) and (C -> D)
Similar to the Java “||” operator the or operator requires either one of the expressions to turn true before the whole expression turns true.
Look for either event A or event B. As always, A and B can itself be nested expressions as well.
A or B
Detect all stock ticks that are either above or below a threshold.
every (StockTick(symbol='IBM', price < 100) or StockTick(symbol='IBM', price > 105)
The not operator negates the truth value of an expression. Pattern expressions prefixed with not are automatically defaulted to true.
This pattern matches only when an event A is encountered followed by event B but only if no event C was encountered before event B.
( A -> B ) and not C
The followed by -> operator specifies that first the left hand expression must turn true and only then is the right hand expression evaluated for matching events.
Look for event A and if encountered, look for event B. As always, A and B can itself be nested event pattern expressions.
A -> B
This is a pattern that fires when 2 status events indicating an error occur one after the other.
StatusEvent(status='ERROR') -> StatusEvent(status='ERROR')
The timer:within guard acts like a stopwatch. If the associated pattern expression does not turn true within the specified time period it is stopped and permanently false. The timer:within guard takes a time period (see Section 6.2.1, “Specifying Time Periods”) or a number of seconds as a parameter.
This pattern fires if an A event arrives within 5 seconds after statement creation.
A where timer:within (5 seconds)
This pattern fires for all A events that arrive within 5 seconds. After 5 seconds, this pattern stops matching even if more A events arrive.
(every A) where timer:within (5 seconds)
This pattern is similar to the first pattern but here every time A arrives within 5 seconds, the pattern begins looking for A for another 5 seconds. As long as A events arrive within 5 seconds after the last A, the pattern does not stop matching.
every (A where timer:within (5 sec))
This pattern matches for any one A or B event in the next 5 seconds.
( A or B ) where timer:within (5 sec)
This pattern matches for any 2 errors that happen 10 seconds within each other.
every (StatusEvent(status='ERROR') -> StatusEvent(status='ERROR') where timer:within (10 sec))
The following guards are equivalent:
timer:within(2 minutes 5 seconds) timer:within(125 sec) timer:within(125)
The timer:interval observer waits for the defined time before the truth value of the observer turns true. The observer takes a time period (see Section 6.2.1, “Specifying Time Periods”) or a number of seconds as a parameter.
After event A arrived wait 10 seconds then indicate that the pattern matches.
A -> timer:interval(10 seconds)
The pattern below fires every 20 seconds.
every timer:interval(20 sec)
The next example pattern fires for every event A that is not followed by an event B within 60 seconds after event A arrived. B must have the same "id" property value as A.
every a=A -> (timer:interval(60 sec) and not B(id=a.id))
The timer:at observer is similar in function to the Unix “crontab” command. At a specified time the expression turns true. The at operator can also be made to pattern match at regular intervals by using an every operator in front of the timer:at operator.
The syntax is: timer:at (minutes, hours, days of month, months, days of week [, seconds]).
The value for seconds is optional. Each element allows wildcard * values. Ranges can be specified by means of lower bounds then a colon ‘:’ then the upper bound. The division operator */x can be used to specify that every xth value is valid. Combinations of these operators can be used by placing these into square brackets([]).
This expression pattern matches every 5 minutes past the hour.
every timer:at(5, *, *, *, *)
The below at operator pattern matches every 15 minutes from 8am to 5pm on even numbered days of the month as well as on the first day of the month.
timer:at (*/15, 8:17, [*/2, 1], *, *)