In an EPL monitor, an on statement specifies an event expression and a listener action.
When the correlator executes an on statement it creates an event listener. An event listener observes each event processed by the context until an event or a pattern of events matches the pattern specified in the event listener’s event expression. When this happens the event listener triggers, causing the correlator to execute the listener action. At this point, depending on the form of the event expression, the event listener either terminates or continues listening for additional matching event patterns.
An event listener analyzes the event stream until one of the following happens:
The event listener finds the pattern defined in its event expression.
The quit() method is called on the event listener to kill it.
The monitor that defines the event listener dies.
The correlator determines that the event listener can never trigger.
The correlator can support large numbers of concurrent event listeners each watching for an individual pattern.
About event expressions and event templates
To create an event listener, you must specify an event expression. An event expression
Identifies an event or event pattern that you want to match
Contains zero or more event templates
Contains zero or more event operators
An event template specifies an event or any type and encloses in parentheses the set of, or set of ranges of, event field values to match. An event template can specify wildcards for event fields or can specify that certain event fields must have particular values or ranges of values.
An event expression can specify a temporal operator and zero event templates.
Following are event expressions that are each made up of one event template:
Event Expression
Description
StockTick(*,*)
The event listener that uses this event expression is interested in all StockTick events regardless of the event’s field values.
NewsItem("ACME",*)
The event listener that uses this event expression is interested in NewsItem events that have a value of ACME in their first field. Any value can be in the second field.
ChainedResponse(reqId="req1")
The event listener that uses this event expression is interested in ChainedResponse events whose reqId field has a value of req1. If a ChainedResponse event has any other fields, their values are irrelevant.
any()
The event listener that uses this event expression is interested in all events, regardless of their type.
You can specify more than one event template in an event expression by adding event operators. The following table describes the operators that you can use in an event expression.
Category
Operator
Operation
Followed by
->
The event listener detects a match when it finds an event that matches the event template specified before the followed-by operator and later finds an event that matches the event template that comes after the followed-by operator. See also Defining event listeners for patterns of events.
Repeat matching
all
The event listener detects a match for each event that matches the specified event template. The event listener does not terminate after the first match. See also Listening for all events of a particular type. Important:
When using the all operator with operators that combine multiple event templates (such as or, xor, ->), it is likely that you want the all to apply to the whole pattern, rather than the individual event templates. To do this, you need to use brackets around the whole event expression, for example, on all (A() or B()). Using on all A() or B() or on all A() or all B() has different and potentially unwanted behavior, particularly if you use the same event type in more than one event template and the templates are not mutually exclusive.
Logical operators
and
Logical intersection. The event listener detects a match after it finds events that match the event templates on both sides of the and operator. The order in which the listener detects the matching events does not matter. See also Specifying the ‘and’ operator in event expressions.
not
Logical negation. The event listener detects a match only if an event that matches the event template that follows the not operator has not occurred. See also Specifying the ’not’ operator in event expressions.
Logical exclusive or. The event listener detects a match if it finds an event that matches exactly one of the event templates on either side of the xor operator. For example, consider this event: A(1,1). This event does not trigger the following listener because it matches the event templates on both sides of the xor operator: on A(1,*) xor A(*,1). See also Specifying the ‘or’ or ‘xor’ operator in event expressions.
event Test
{
float f;
}
monitor RangeExample
{
action onload()
{
on Test (f >= 9.0 ) and Test (f <= 10.0) processTest();
}
action processTest();
{
do something
}
}
The event expression is:
Test (f >= 9.0 ) and Test (f <= 10.0)
This event expression specifies the and operator so the event listener must detect an event that matches both of the event expression’s event templates or two events, where one matched the first template and another matched the second. It does not have to be a single event that matches both event templates. The order in which the templates are matched does not matter.
Consider this event expression:
A(a = "foo") xor A(b > 9)
An event listener that defines this event expression triggers for A("foo", 9) but not A("foo", 10). On A("foo", 10), the A templates would trigger simultaneously, so the xor would remain false.
The correlator can match on up to 32 fields per event. If you specify an event template for an event that has more than 32 fields, you must ensure that the correlator maintains indexes for the particular fields for which you specify values that you want to match.
In other words, when the event definition was loaded into the correlator, the fields that did not have the wildcard keyword formed the set of fields that you can match on. An event template can try to match on only those fields. If an event template specifies any of the wildcard fields, it must be with an asterisk.
If you try to load a monitor that defines an event template that specifies more than 32 fields without an asterisk or a wildcard field without an asterisk, the correlator rejects the monitor. You must correct the template in order to load the monitor.
Specifying the on statement
You specify an on statement to define an event listener. The format of an on statement is as follows:
[listener identifier := ] on event_expr [ coassignment ] listener_action;
Syntax description
Syntax Element
Description
identifier
Optionally, you can specify a variable of type listener and assign the new event listener to that variable. This gives you a handle to the event listener — if you want to terminate it you can call the quit() method on the listener.
event_expr
The event expression identifies the event or pattern of events that you want to match. An event expression is made up of one or more event templates and zero or more event operators.
coassignment
Optionally, you can coassign the matching event to a variable. Use the as operator to implicitly declare the variable in the scope of the following listener_action, or the : assignment operator to coassign to a local variable or monitor variable of the same event type. Coassignments are part of event templates. Each event template can have a coassignment, so there can be multiple coassignments in a listener.
The statement or block that you want the correlator to perform when the event listener triggers.
Examples
In the following example, the event expression contains one event template: StockTick(*,*). The asterisks indicate that the values of the StockTick event’s two fields are not relevant when matching. When this event listener detects a StockTick event, the listener triggers and causes the correlator to execute the processTick() listener action.
on StockTick(*,*) processTick();
Following is an example that implicitly declares the newTick variable in the scope of the listener_action.
on StockTick(*,*) as newTick {
processTick(newTick);
}
Following is an example that explicitly coassigns the matching event to the newTick variable. The newTick variable must be a StockTick event type variable. Coassignment simply assigns the event to the variable.
on StockTick(*,*):newTick processTick();
The next example begins with the declaration of a listener variable. The statement assigns the event listener to the l variable.
listener l := on StockTick(*,*) as newTick processTick();
Suppose that after finding a matching event, the listener action includes specification of an on statement. For example:
listener l := on StockTick(*,*) as newTick {
on StockTick(newTick.symbol, > newTick.value) as risingTick {
processRisingTick();
}
}
The correlator creates an entirely new event listener to handle the nested on statement. This new event listener is completely independent of the enclosing event listener. For example, the enclosing event listener does not wait for the nested event listener to find a matching event.
Using a stream source template to find events of interest
In addition to event listeners, EPL provides stream source templates for finding events of interest. A stream source template is an event template prefixed with the all keyword. The result of a stream source template is a stream.
Use streams on a continuous flow of incoming items when you want to aggregate, join to other streams, and/or narrow the scope of the matching items based on content, arrival time, or the most recent particular number of items.
Use an event listener for discrete events or discrete patterns of events for which you want to independently trigger the listener action.
Defining event expressions with one event template
This section provides examples of specifying event expressions that contain just one event template. It is important to understand the various ways that you can specify a single event template. When you are familiar with this, it is easier to start applying operators and combining multiple event templates in an event expression.
Listening for one event
Consider the following on statement:
on StockTick() processTick();
This event listener is watching for one StockTick event. The values of the StockTick event’s fields are irrelevent, as indicated by the empty parentheses. When this event listener finds a StockTick event, it triggers and terminates. When the event listener triggers, it causes the correlator to execute the processTick() action.
Listening for all events of a particular type
Consider the straightforward case where an event expression consists of a single event template. When the event listener finds an event that matches its event template, the event listener triggers, and the correlator executes the listener action. Since the event listener has found the event it was looking for, it terminates.
In some situations, you might want the event listener to continue watching for the same event so that you can act on each one. You do not want the event listener to terminate after it finds one event. In this situation, specify the all keyword before the event template, as in the following example:
on all StockTick() processTick();
When the all operator appears before an event template, when that event template finds a match, it continues to watch for subsequent events that also match the template.
Listening for events with particular content
The sample monitor is very simple. It just logs all StockTick events. The content of the StockTick event is not relevant when matching. See Example of a simple monitor. However, you can filter events according to their content. To alter the example so that the monitor logs only StockTick events for a given stock, you must specify a filter on the first field in the event template. For example, suppose you want to log only ACME stock ticks. You need to change the following line:
on all StockTick(*,*):newTick processTick();
to this:
on all StockTick("ACME",*):newTick processTick();
Now the event listener triggers on only StockTick events whose name field matches ACME.
To filter StockTick events based on their price, you might specify the event template shown below. This event template specifies that you are interested in all StockTick events whose price is 50.5 or greater.
on all StockTick(*, >=50.5):newTick processTick();
Using positional syntax to listen for events with particular content
You can specify that you want to listen for StockTick events that have a particular name and a particular price. In the on statement below, the event listener is looking for StockTick events in which the name is ACME and the price is 50.5 or less.
on all StockTick("ACME", <=50.5) as newTick processTick();
When you specify this syntax, called positional syntax, the event template can define a value (or a wildcard) to match against for every field of that event’s type. You must specify these values in the same order as the fields in the event type definition. If you specify less than the total number of members, the remaining fields are implicitly defined as a wildcard. Consider the following event type:
on MobileUser(*,*, "red", "Capricorn") some_action();
on MobileUser(*,*, "red", "Capricorn", *, *, *, *, *) *some\_action*();
Using name/value syntax to listen for events with particular content
Specification of every field in an event can get unwieldy when you are working with event types with a large number of fields and you are specifying values for only a few of them. In this case, you can use the name/value syntax in which you specify only the fields of interest. In the name/value syntax, it is as if you had specified a wildcard (*) for each field for which you do not specify a value. For example:
on MobileUser(hairColour="red", starsign="Capricorn",
preferredGender=1) *some\_action*();
The table below shows equivalent event expressions that demonstrate how to specify each syntax. The table uses these event types:
event A {
integer a;
string b;
}
event B {
integer a;
}
event C {
integer a;
integer b;
integer c;
}
Comparison Criterion
Positional Syntax
Equivalent Name/Value Syntax
Equality
on A(3,"string")on A(=3,="string")
on A(a=3,b="string")on A(b="string",a=3)
Relational comparisons
on B(>3)
on B(a>3)
Ranges
on B([2:3])
on B(a in [2:3])
Wildcards
on C(*,4,*)on C(*,*,*)
on C(b=4)on C(a=*,b=4,c=*)
on C()
For details about the operators and expressions that you can specify in event templates, see [Expressions](/epl-reference/expressions/).
It is possible to mix the two syntax styles as long as you specify all positional fields before named fields. For example:
Correct event template: on D(3,>4,i in [2:4])
Incorrect event template: on D(k=9,"error")
Listening for events of different types
A monitor is not limited to listening for events of only one type. A single monitor can listen for any number of event types. The following sample monitor uses the StockTick event type and the StockChoice event type, which specifies a stock name. When the event listener finds a StockChoice event, a second event listener then looks for only stocks that match the name in the StockChoice event.
// Definition of a type of event that the correlator will receive.
// These events represent stock ticks from a market data feed.
event StockTick {
string name;
float price;
}
// Definition of a type of event that describes the stock to process.
// These events come from a second live data feed.
event StockChoice {
string name;
}
// The following simple monitor listens for two different event types.
monitor SimpleShareSearch {
// A global variable to store the matching StockTick event:
StockTick newTick;
// A global variable to store the StockChoice event:
StockChoice currentStock;
// Wait for a StockChoice event and use its name field to
// filter for StockTick events.
action onload() {
on StockChoice(*):currentStock {
on all StockTick(currentStock.name, *):newTick processTick();
}
}
action processTick() {
log "StockTick event received" +
" name = " + newTick.name +
" Price = " + newTick.price.toString() at INFO;
}
}
Definition of an additional event type (StockChoice)
Definition of a new global variable (currentStock)
A more complex onload() action
While the first two changes are straightforward, the new onload() action introduces new behavior. The first line in the onload() action is similar to that in the earlier example. In the new example, the monitor creates an event listener for a StockChoice event. The content of the StockChoice event is not relevant when matching. When the event listener finds an event of this type, it stores the value of the StockChoice``name field in the currentStock variable and triggers the creation of a second event listener.
In this example, the first event listener defines the action of creating the second event listener in-line. The first event listener looks for a StockChoice event. The second event listener looks for all StockTick events whose name field corresponds to the value of currentStock.name.
Listening for events of all types
You can specify a listener which listens for all events types as shown below:
on any() as anyVar {
print "Event received : " + anyVar.toString();
}
In the above listener, the on any() event template will match against all events in the corresponding context, regardless of their type.
You can provide an optional typeName to specify which type you are listening for. This must name a routable type.
You can provide an optional values. This must be a dictionary<string, any> which names indexable fields. The listener will filter to only match events where all of the specified fields are equal. If you specify values, you must also specify the typeName. For example:
event A{}
event B{}
on all any() as anyVar {
print "Event received : " + anyVar.toString();
}
route A(); //listener fires
route B(); //listener fires
You can use all event expression operators (all, and, not, -> and so on) with the any listener.
Info
Due to unpredictable results, it is recommended that you do not use any() listeners in onload actions. Instead, do so in response to an event (a start event or, more likely, a configuration event) once everything has been injected.
Terminating and changing event listeners
After the correlator creates an event listener, you cannot change it. Instead of changing an event listener, you terminate it and create a new one.
The example in Listening for events of different types looks for only one StockChoice event. The monitor would be more useful if it continued looking for subsequent StockChoice events, and on every new StockChoice event it changed the second event listener to look for stock ticks for the new company.
When the correlator creates an event listener, it copies from the action the value of any local variables. However, if the variable is of a reference type, changes to the object referred to by the value are seen by other listeners.
When you want to change an event listener, do the following:
Obtain a handle to the event listener you want to change.
Terminate that event listener with the quit() operation.
Create a new event listener to take its place.
The following sample monitor does just this.
// Definition of a type of event that the correlator will receive.
// These events represent stock ticks from a market data feed.
event StockTick {
string name;
float price;
}
// Definition of a type of event that describes the stock to process.
// These events come from a second live data feed.
event StockChoice {
string name;
}
// The following simple monitor listens for two different event types.
monitor SimpleShareSearch {
// A global variable to store the matching StockTick event:
StockTick newTick;
// A global variable to store the StockChoice event:
StockChoice currentStock;
// A handle to the second listener:
listener l;
// Record the latest StockChoice event and use its name field
// to filter the StockTick events.
action onload() {
on all StockChoice(*):currentStock {
l.quit();
l := on all StockTick(currentStock.name, *):newTick processTick();
}
}
action processTick() {
log "StockTick event received" +
" name = " + newTick.name +
" Price = " + newTick.price.toString() at INFO;
}
}
The monitor in this example declares an additional global variable, l, whose type is listener.
The initial on statement now specifies the all operator. After this event listener finds a StockChoice event, it watches for the next StockChoice event.
The onload() action specifies a new listener action. Each time the first event listener finds a StockChoice event, the listener action:
Terminates the second event listener by calling the l.quit() method. Of course, upon finding the first StockChoice event there is no second event listener to terminate. This is not a problem as in this case the l.quit() method does not do anything.
Creates a new event listener to seek StockTick events for the company named in the StockChoice event just detected.
Stores a handle to the new event listener in the l global variable. The first event listener uses this handle when it needs to terminate the second event listener.
Specifying multiple event listeners
When the correlator encounters an on statement, it creates an event listener to watch for events that match the event expression specified in the on statement. When the event listener finds a matching event, the event listener triggers and the correlator executes the listener action. Ordinarily the event listener then dies. That is, the event listener processes only a single matching event.
When you require multiple matching events specify the all operator before the template for the event for which you want multiple matches. This prevents termination of the event listener upon an event match.
Another way to match multiple events is to define two (or more) event listeners for the same event type. If you specify two on statements that require the same event, they both trigger when they find that event. The order in which they trigger is not defined. For example:
on all StockTick(*,*) as newTick1 { print newTick1.name; }
on all StockTick(*,*) as newTick2 { print newTick2.name; }
When the correlator receives a single StockTick event, the correlator populates both the newTick1 variable and the newTick2 variable with the event value. The correlator then prints the value of the name field in each variable. This means that an event of the format StockTick("ACME", 50.10) causes this output:
ACME
ACME
Adding further on statements to those above would increase the number of times the string ACME is printed. This is true regardless of where (that is, in which action) the on statements are defined. For example:
action action1() {
on all StockChoice("ACME") as currentStock processTick();
}
action action2() {
on all StockChoice("ACME") as currentStock processTick();
}
If both the action1() and action2() actions have been invoked, both will invoke the processTick() action when an “ACME” StockChoice event is received.
Now consider the following example:
on all StockTick("ACME", *) action1();
on all StockTick(*,50.0) action1();
The event StockTick("ACME", 50.0) will trigger both event listeners. It is not possible to determine which one will execute the action first but the actions will be executed atomically. That is, the correlator will start executing action1(), finish it, and only then will the correlator execute action1() again. The correlator processes only one listener action at a time.
Sometimes it is useful to catch events that do not match other event templates. To do this, specify the unmatched keyword in an event template. An unmatched event template matches against events for which both of the following are true:
Except for completed and unmatched event templates, the event does not cause any other event expression in the same context as the unmatched event template to match. For information about completed event templates, see the next topic.
The event matches the unmatched event template.
The correlator processes an event as follows:
The correlator tests the event against all normal event templates. Normal event templates do not specify the completed or unmatched keyword.
If the correlator does not find a match, the correlator tests the event against all event templates that specify the unmatched keyword. If the correlator finds one or more matches, the matching event templates now evaluate to true. That is, if there are multiple unmatched event templates that match the event, they all evaluate to true.
The scope of an unmatched event template is the context that contains it. Suppose an event goes to two contexts. In one context, there is a matching event listener and in the other context there is a match against an unmatched event template. Both matches trigger the listener actions.
Specify the unmatched keyword with care. Be sure to communicate with other developers. If your code relies on an unmatched event template, and someone else injects a monitor that happens to match some events that you expected to match your unmatched event template, you will not get the results you expect. The firing of an event-specific unmatched listener is suppressed if an on any() listener matches the event (not just a type-specific listener).
A typical use of the unmatched keyword is to spawn a monitor instance to process a particular subset of events. For example:
event Tick{ string stock;... }
monitor TickProcessor {
Tick tick;
...
action onload() {
on all unmatched Tick():tick spawn processTick();
}
action processTick() {
on all Tick( stock=tick.stock )...;
}
...
}
You can use the unmatched keyword with an any() listener as shown below:
In some situations, you want to ensure that the correlator completes all work related to a particular event before your application performs some other work. In your event template, specify the completed keyword to accomplish this. For example:
on all completed A(f < 10.0) {}
Suppose an A event whose f field value is less than 10 arrives in the correlator. What happens is as follows:
If there are normal or unmatched event listeners whose event expression matches this A event, those event listeners trigger.
The correlator executes listener actions and then processes any routed events that result from those actions, and any routed events that result from processing the routed events, and so on until all routed events have been processed.
The completed event listener triggers.
A common situation in which the completed keyword is useful is when a piece of data comes into the system and that piece of data causes a cascade of event listeners to trigger. Each listener action updates some data. When all listener actions have been executed, you want to take a survey of the new state of things and do something in response.
For example, consider a pricing engine made up of many individual pricing engines. When a new piece of market data arrives all pricing engines update their prices and then the controller uses some metric to pick the best price, which it publishes. The controller should publish the new price only after all individual engines have updated their output. The controller achieves this by listening for all the updates but only publishing when the market data event causes the completed event listener to trigger. The EPL for this scenario follows.
// Request/return best price from *all* markets
event RequestSmartBestPrice{ string stock; integer id; }
event BestSmartPriceReply{ integer id; float price; }
//Request/return best price from individual market(s)
event RequestBestPrice{ string stock; integer id; }
event BestPriceReply{ integer id; float price; }
// Simple example: Assume 'best' is 'lowest' and no account
// is taken of 'side'.
monitor SmartPriceGetter {
RequestSmartBestPrice request;
sequence< float > prices;
action onload() {
on all RequestSmartBestPrice(*,*):request spawn getPrices();
}
action getPrices() {
on all BestPriceReply( request.id, * ) as reply
prices.append(reply.price);
on completed RequestSmartBestPrice( request.stock, request.id ) {
prices.sort();
route BestSmartPriceReply( request.id, prices[0]);
die;
}
route RequestBestPrice( request.stock, request.id );
}
}
You can use the completed keyword with an any() listener as shown below:
on completed any() {
print "A completed any() listener triggered!";
}
A completed any() event template will match against all events in the context once they are processed, regardless of their type.
Example using unmatched and completed
The following example shows the use of both the unmatched and completed keywords. After the example, there is a discussion of the processing order.
on all A("foo", < 10) as a {
print "Match: " + a.toString();
a.count := a.count+1; // count is second field of A
route A;
}
on all completed A("foo", < 10) as a {
print "Completed: " + a.toString();
}
on all unmatched A(*,*)as a {
print "Unmatched: " + a.toString();
}
A("foo", 8) is the first item on the queue. The correlator processes all matches for this event except for any matching on completed expressions. The correlator processes those after it has processed all routed events originating from A("foo", 8), which includes the processing of all routed events produced from all routed events produced from A("foo", 8), and so on.
Correlator processing goes like this:
Processing of A("foo", 8) routes A("foo", 9) to the front of the queue.
Processing of A("foo", 9) routes A("foo", 10) to the front of the queue.
Processing of A("foo", 10) finds a match with the unmatched event expression.
All routed events that resulted from A("foo", 9) have now been processed. The completed A("foo", 9) event template now matches so the correlator executes its listener action.
All routed events that resulted from A("foo", 8) have now been processed. The completed A("foo", 8) event template now matches so the correlator executes its listener action.
Processing of A("bar", 7) matches the unmatched A(*,*) event template and the correlator executes its listener action.
Improving performance by ignoring some fields in matching events
In applications where a particular field of an event type will never participate in the match criteria for that event type, the performance of Apama can be improved (at times drastically) by marking that field as a wildcard field in the event type definition.
For example, consider a version of the StockTick event type that has four fields: name, volume, price, and source. If in our application volume and source are never going to be used for matching on within event templates, that is, they will always be marked as * (wildcard), they could be tagged so explicitly in the event type:
The wildcard keyword tells Apama not to include this field in its internal indexing, as it will never be required in a match operation. This not only saves memory, but can significantly improve performance, particularly when there are many such fields which never occur in match conditions. Note that removing fields from an event type altogether is even more efficient than using wildcard, but this is not always possible. For example, the field might not be relevant in match conditions but it might be input to calculations within an action block, or it might need to be included in an event specified in a send...to statement.
When a field has been declared as a wildcard then any subsequent attempt to define a match condition using that field will result in a parser error, and the offending monitor will not be injected.
Therefore, given the above event type definition, the following will result in a parser error:
action someAction() {
on StockTick("ACME", >125.0,*,"NASDAQ") doSomething();
}
while the following is correct:
action someAction() {
on StockTick("ACME", *, 50.0, *) doSomething();
}
Defining event listeners for patterns of events
One way to search for an event pattern in EPL is to define an event listener to search for the first event, and then, in that listener action, define a second event listener to search for the second event in the pattern, and so on.
However, the on statement takes an event expression, and this can be more than just a single event template.
Consider the following very simple example: locate a news event about ACME followed by a stock price update for ACME. With the EPL explored so far, one would write this as:
event StockTick {
string name;
float price;
}
event NewsItem {
string subject;
string newsHeading;
}
monitor NewsSharePriceSequence_ACME {
// Look for a news item about ACME, if successful execute the
// findStockChange() action
//
action onload() {
on NewsItem("ACME",*) findStockChange();
}
// Look for a StockTick about ACME, if successful execute the
// notifyUser() action
//
action findStockChange() {
on StockTick("ACME",*) notifyUser();
}
// Print a message, event sequence detected
//
action notifyUser() {
log "Event sequence detected.";
}
}
If, as in this example, you do not intend to express any custom actions after finding an event other than searching for another event, the whole pattern of events to look for can be encoded in a single event expression within a single on statement.
An event expression can define a pattern of events to match against. Each event of interest is represented by its own event template. You can apply several constraints on the temporal order that the events have to occur in to match the event expression.
In the more declarative syntax of an event expression, the above monitor would be written as follows:
event StockTick {
string name;
float price;
}
event NewsItem {
string subject;
string newsHeading;
}
monitor NewsSharePriceSequence_ACME {
// Look for a NewsItem followed by a StockTick
action onload() {
on NewsItem("ACME",*) -> StockTick("ACME",*)
notifyUser();
}
// Print a message, event sequence detected
//
action notifyUser() {
log "Event sequence detected.";
}
}
Here, instead of just one event template, the on keyword is now followed by an event expression that contains two event templates.
The primary operator in event expressions is ->. This is known as the followed-by operator. It allows you to express a pattern of events to match against in a single on statement, with a single action to be executed at the end once the whole pattern is encountered.
In EPL, an event pattern does not imply that the events have to occur right after each other, or that no other events are allowed to occur in the meantime.
Let A, B, C and D represent event templates, and A', B', C' and D' be individual events that match those templates, respectively. If a monitor is written to seek (A > B), the event feed {A',C',B',D'} would result in a match once the B' is received by Apama.
Followed-by operators can be chained to express longer patterns. Therefore, one could write:
on A -> B -> C -> D executeAction();
Notes:
An event template is in fact the simplest form of an event expression. All event expression operators, including ->, actually take event expressions as operands. So in the above representation, A, B, C, D could in fact be entire nested event expressions rather than simple event templates.
It is useful to think of event expressions as being Boolean expressions. Each clause in an event expression can be true or false, and the whole event expression must evaluate to true before the event listener triggers and the action is executed.
Consider the above event expression: A -> B -> C -> D
The expression starts off as false. When an event that satisfies the A event template occurs, the A clause becomes true. Once B is satisfied, A -> B becomes true in turn, and evaluation progresses in a similar manner until eventually all of A -> B -> C > D evaluates to true. Only then does the event listener trigger and cause execution of the listener action. Of course, this event expression might never become true in its entirety (as the events required might never occur) since no time constraint (see Defining event listeners with temporal constraints) has been applied to any part of the event expression.
Specifying and/or/xor/not logic in event listeners
When the correlator creates an event listener, each event template in the event expression is initially false. For an event listener to trigger on an event pattern, the event expression defining what to match against must evaluate to true. Consequently, in an event expression, you can specify logical operators.
Specifying the ‘or’ or ‘xor’ operator in event expressions
The or and xor operators let you specify event expressions where a variety of event patterns could lead to a successful match. They effectively evaluate two event templates (or entire nested event expressions) simultaneously and return true when either of them becomes true (or) or when either of them becomes true and the other is false (xor).
Example for the or operator:
on A() or B() executeAction();
This means that either A or B need to be detected to match. That is, the occurrence of one of the operand expressions (an A or a B) is enough for the event listener to trigger.
Info
The examples in this section show how to use the or operator, but they similarly apply to the xor operator.
A coassigned variable can only be used if the variable is initialized in all circumstances that could cause the listener to fire. The following are both valid and invalid examples for coassignment.
Info
Important:
When using the all operator with operators that combine multiple event templates (such as or, xor, ->), it is likely that you want the all to apply to the whole pattern, rather than the individual event templates. To do this, you need to use brackets around the whole event expression, for example, on all (A() or B()). Using on all A() or B() or on all A() or all B() has different and potentially unwanted behavior, particularly if you use the same event type in more than one event template and the templates are not mutually exclusive.
Example 1 - valid
Only one of these A events will have been matched when triggered, but both are coassigned to variable a, so you can use a in the action.
on A(property = value1) as a or A(property = value2) as a {
executeAction(a);
}
Example 2 - valid
Only one of a or b will be coassigned to. However, they are global variables, so if a is coassigned to, you will have the default initialized value of b or the old value of b from a previous triggering.
monitor Monitor {
A a;
B b;
action foo() {
on all (A(): a or B(): b) {
executeAction(a, b);
}
}
}
Example 3 - valid
Only one of a or b will be coassigned to when triggered. However, they already have values assigned, so if a is coassigned to, you will have a new B in b.
monitor Monitor {
action foo() {
A a := new A;
B b := new B;
on all (A(): a or B(): b) {
executeAction(a, b);
}
}
}
Example 4 - invalid
Only one of a or b will be coassigned to when triggered. Neither of them already have values, so you cannot assume that either a or b can be used.
monitor Monitor {
action foo() {
A a;
B b;
on all (A(): a or B(): b) {
executeAction(a, b);
}
}
}
Example 5 - invalid
This is basically the same as example 4.
on all (A() as a or B() as b) {
executeAction(a, b);
}
Specifying the ‘and’ operator in event expressions
The and operator specifies an event pattern that might occur in any temporal order. It evaluates two event templates (or nested event expressions) simultaneously but only returns true when they are both true.
on A() and B() executeAction();
This will seek “an A followed by a B” or “a B followed by an A”. Both are valid matching patterns, and the event listener will seek both concurrently. However, the first to occur will terminate all monitoring and cause the event listener to trigger.
Example event expressions using ‘and/or’ logic in event listeners
The following example event expressions indicate a few patterns that can be expressed by using and/or logic in event listeners.
Event Expression
Description
A -> (B or C)
Match on an A followed by either a B or a C.
(A -> B) or C
Match on either the pattern A followed by a B, or just a C on its own.
A -> ((B -> C) or (C -> D))
Find an A first, and then seek for either the pattern B followed by a C or C followed by a D. The latter patterns will be looked for concurrently, but the monitor will match upon the first complete pattern that occurs. This is because the or operator treats its operands atomically, that is, in this case it is looking for the patterns themselves rather than their constituent events.
(A -> B) and (C -> D)
Find the pattern A followed by a B (that is, A -> B) followed by the pattern C -> D, or else the pattern C -> D followed by the pattern A -> B. The and operator treats its operands atomically. That is, in this case it is looking for the patterns themselves and the order of their occurrence, rather than their constituent events. It does not matter when a pattern starts but it occurs when the last event in it is matched. Therefore {A',C',B',D'} would match the specification, because it contains an A -> B followed by a C -> D. In fact, the specification would match against either of the following patterns of event instances; {A',C',B',D'}, {C',A',B',D'}, {A',B',C',D'}, {C',A',D',B'}, {A',C',D',B'} and {C',D',A',B'}
Specifying the ’not’ operator in event expressions
The not operator is unary and acts to invert the truth value of the event expression it is applied to.
on ((A() -> B()) and not C()) executeAction();
therefore means that the event listener will trigger executeAction only if it encounters an A followed by a B without a C occurring at any time before the B is encountered.
The not operator can cause an event expression to reach a state where it can never evaluate to true. That is, it becomes permanently false.
Consider the above event listener event pattern: on (A() -> B()) and not C()
The event listener starts by seeking both A -> B and not C concurrently. If an event matching C is received before one matching B, the C clause evaluates to true, and hence not C becomes false. This means that (A -> B) and not C can never evaluate to true, and hence this event listener will never trigger. The correlator terminates these zombie event listeners periodically.
It is possible to specify the not operator in an event expression in such a way that the expression always evaluates to true immediately. Since this triggers the specified action without any events occurring, you want to avoid doing this. See Avoiding event listeners that trigger upon instantiation.
Specifying ‘and not’ logic to terminate event listeners
A typical situation is that you want to listen for a pattern only until a particular condition occurs. When the condition occurs you want to terminate the event listener. In pseudocode, you want to specify something like this:
on all event_expression until stop_condition
To define an event listener that behaves this way, you specify and not:
on all event_expression and not stop_condition
The following example listens for a price increase for a particular stock while the market is open.
event Price {
string stock;
float price;
}
on all Price("IBM",>targetPrice) as p and not MarketClosed() {
... do something}
When you inject a monitor that contains this code, the correlator sets up an event template to listen for Price events and another event template to listen for MarketClosed events. As long as the correlator does not receive a MarketClosed event, not MarketClosed() evaluates to true. While not MarketClosed() evaluates to true, each time the correlator receives a Price event for IBM stock at a price that is greater than targetPrice, this event expression evaluates to true and triggers its listener action. When the correlator receives a MarketClosed event, MarketClosed() evaluates to true and so not MarketClosed() evaluates to false. At that point, the event expression can no longer evaluate to true. When the correlator recognizes an event listener that can never trigger, it terminates it. In other words, after the market is closed the event listener terminates.
Typically, the stop condition is a condition that applies to multiple entities. In the previous example, the condition applies to only IBM stock, but it could easily be rewritten to apply to all stocks.
Pausing event listeners
You can also specify and not when you want to listen for a pattern, pause when a particular condition occurs, and resume listening for that pattern when some other condition occurs. Consider the example that terminates the event listener after the market closes. Suppose instead that you want to listen for increases in stock prices only when there is no auction. When the correlator receives an InAuction event, you want to pause the event listener and when the correlator receives an AuctionClosed event you want the event listener to become active again. To do this, you can write something like the following:
action initialize() {
on EndAuction() and not BeginAuction() notInAuctionLogic();
on BeginAuction() and not EndAuction() inAuctionLogic();
route RequestAuctionPhase();
}
action inAuctionLogic() {
on EndAuction() notInAuctionLogic();
}
action notInAuctionLogic() {
on all Price("IBM",>targetPrice) as p and not BeginAuction()
sellStock();
on BeginAuction() inAuctionLogic();
}
The initialize() action sets up two event listeners that determine whether to start with the inAuctionLogic() action or the notInAuctionLogic() action. The response to the routed RequestAuctionPhase event is an EndAuction event or a BeginAuction event. As soon as one of these events arrive, both event listeners terminate. For example, if an EndAuction event arrives, the first event listener terminates because its EndAuction() event template evaluates to true and its not BeginAuction() event template also evaluates to true. The second event listener terminates because its not EndAuction() event template evaluates to false and so the event expression can never evaluate to true.
Choosing which action to execute
Another situation in which and notlogic can help terminate event listeners is when you want to specify a choice of one or more actions and terminate the event listeners after one is chosen. An example of this appears below. This is the CEP equivalent of a case statement.
on Pattern_1() and not PatternMatched() processCase1();
on Pattern_2() and not PatternMatched() processCase2();
on Pattern_3() and not PatternMatched() processCase3();
on Pattern_1() or Pattern_2() or Pattern_3()
{
route PatternMatched();
}
When you inject a monitor that contains this type of code the correlator immediately sets up multiple event listeners. For the example in Pausing event listeners, the event listeners would be watching for these events:
Pattern_1
PatternMatched
Pattern_2
Pattern_3
Initially, all and not event templates evaluate to true. Suppose Pattern_2 arrives. This causes these two event listeners to trigger:
on Pattern_2() and not PatternMatched() processCase2();
on Pattern_1() or Pattern_2() or Pattern_3()
It is unknown which event listener action the correlator executes first, but the order does not matter. The correlator does all of the following:
The correlator executes the processCase2() action.
The correlator terminates the event listener that specifies processCase2() because it has found its match and it does not specify all.
The correlator routes a PatternMatched event to the front of the context’s input queue.
When the correlator processes the PatternMatched() event, the two event templates that are still watching for and not PatternMatched become false. Consequently, those event listeners will never trigger and the correlator terminates them.
Following is another example of specifying and notto make a choice:
on Ack() and not Nack()
{
processAck();
}
on Nack() and not Ack()
{
processNack();
}
Specifying ‘and not’ logic to detect when events are missing
Using and not logic with a time-based listener is useful for detecting the absence of an event that is expected.
For example, consider an application that monitors the processing of customer orders. The application listens for OrderCreate events, which indicate that a customer has placed an order. After an OrderCreate event is found, the application listens for an OrderStepComplete event that has an instanceid value that matches the instanceid value in the OrderCreate event and that has a step field value of Order Shipped. If the application does not find a matching OrderStepComplete event within an hour (3600 seconds), the listener triggers and the application generates an alert to indicate that the order was not shipped.
Following is code that shows the listener definition.
on all OrderCreate() as oc {
on wait(3600.00) and not OrderStepComplete(
instanceid=oc.instanceid,step="Order Shipped") as os {
// Raise an alert.
}
}
This listener triggers when the event templates on both sides of the and operator evaluate to true. The event template before and evaluates to true after an hour has elapsed. The event template after and evaluates to true in the absence of a matching OrderStepComplete event. If the application finds a matching OrderStepComplete event within an hour then the second event template evaluates to false and the correlator terminates the listener because it can never trigger.
In the following example, when a FileReceived event is found, the application starts to listen for a FileProcessed event. If a FileProcessed event is not found within 30 seconds of receiving the FileReceived event, the application generates an alert.
monitor SimpleFileSearch {
action onload() {
on all FileReceived() as f {
on wait(30.0) and not FileProcessed(id=f.id) {
// Send alert that file was not processed.
}
on FileProcessed(id=f.id) within(30.0) {
// Send confirmation that the file was processed.
}
}
}
}
How the correlator executes event listeners
An understanding of how the correlator executes event listeners can help you correctly define event listeners. The topics below provide the needed background.
How the correlator evaluates event expressions
When the correlator processes an injection request, it executes the monitor’s onload() statement, which typically defines an event listener. To understand how the correlator evaluates the event expression in the event listener, consider the following on statement:
on A()->B() and C()->D() processOrder();
The event expression consists of four templates and three operators. The operators are:
->
and
->
The correlator does not evaluate the right operand of a followed by operator until after its left operand has evaluated to true. Hence, B and D are not evaluated initially but will only be evaluated after A and C, respectively, have become true. Initially, the correlator evaluates the A and C event templates.
Suppose a C event arrives first. The C part of the event expression is now true and the correlator now evaluates the A and D event templates. Now suppose an A event arrives next. The correlator evaluates the B and D event templates. When a B event arrives the first term, A()->B(), of the event expression becomes true. Finally a D event arrives and the second term, B()->D() becomes true and so the expression as a whole evaluates to true. The event listener triggers.
As mentioned before, when the correlator instantiates an event listener each event template in the event listener is initially false. An event template changes to true when the correlator finds a matching event. In a given context, the correlator cannot find a matching event while it is setting up an event listener because the correlator processes only one thing at a time in each context. Everything happens in order and no two things happen simultaneously in a given context.
Of course, events are always coming into the correlator. These events go on the input queue of each public context to wait their turn for processing. So while a matching event might arrive while the correlator is setting up an event listener, as far as correlator processing is concerned, the event arrives later. See Understanding time in the correlator.
Avoiding event listeners that trigger upon instantiation
Because all event templates are initially false, it is important to think carefully before specifying not in an event expression. It is easy to inadvertently specify the not operator in such a way that the expression evaluates to true immediately upon instantiation. Since this triggers the specified action without any events occurring, it is unlikely to be what you intended and you want to avoid doing this. Consider the following example:
on ( A() -> B() ) or not C() myAction();
Assuming that A, B and C represent event templates, the value of each starts as being false. This means that not C is immediately true, and hence the whole expression is immediately true, which triggers the specified action. If any of A, B or C is a nested event expression the same logic applies for its evaluation. Typically, the not operator is used in conjunction with the and operator. See Choosing which action to execute.
When an event listener triggers the correlator sends a request to the front of the context’s input queue to execute the event listener action. For example:
route D();
on not E() {
print "not E";
}
route F();
The route keyword sends the specified event to the front of the context’s input queue. The correlator processes this code in the following order:
The correlator processes event D.
The correlator prints “not E”.
The correlator processes event F.
When the correlator terminates event listeners
The correlator terminates event listeners in the following situations:
The event listener’s event expression evaluates to true, and does not specify the all keyword. The correlator executes the specified action. Since the single defined match was found, the correlator terminates the event listener.
The correlator recognizes that an event listener’s event expression can never evaluate to true. For example:
on ( A() -> B() ) and not C()
The event listener starts by seeking both A() -> B() and not C() concurrently. If an event matching C is received before one matching B, the C clause evaluates to true, and hence not C becomes false. This means that (A() -> B()) and not C() can never evaluate to true, and hence this event listener will never trigger its action. The correlator terminates these zombie event listeners periodically.
How the correlator evaluates event listeners for a series of events
Suppose there are seven event templates defined, which are represented as A, B, C, D, E, F and G. Now, consider a series of incoming events, where Xn indicates an event instance that matches the event template X. Likewise, Xn+1 indicates another event instance that matches against X, but which need not necessarily be identical to Xn.
Consider the following pattern of incoming events:
C1 A1 F1 A2 C2 B1 D1 E1 B2 A3 G1 B3
Given the above event pattern, what should the event expression A() -> B() match upon?
In theory the combinations of events that correspond to “an A followed by a B” are {A1, B1}, {A1, B2}, {A1, B3}, {A2, B1}, {A2, B2}, {A2, B3} and {A3, B3}. In practice it is unlikely that you want your event listener to match seven times on the above example pattern, and it is uncommon for all the combinations to be useful.
In fact, within EPL, on A() -> B() will only match on the first instance that matched the template. Given the above event pattern the event listener will trigger only on {A1, B1}, execute the associated action and then terminate.
Evaluating event listeners for all A-events followed by B-events
You might want to alter the behavior described in the previous topic, and have the event listener match on more of the combinations. To do this, specify the all operator in the event expression.
If the event listener’s specification was rewritten to read:
on all A() -> B() success();
the event listener would match on every A and the first B that follows it.
The way this works is that upon encountering an A, the correlator creates a second event listener to seek the next A. Both event listeners would be active concurrently; one looking for a B to successfully match the pattern specified, the other initially looking for an A. If more As are encountered the procedure is repeated; this behavior continues until either the monitor or the event listener are explicitly killed.
Therefore on all A() -> B() would return {A1, B1}, {A2, B1} and {A3, B3}.
Note that all is a unary operator and has higher precedence than ->, or and and. Therefore
on all A() -> B()
is the same as both of the following:
on (all A()) -> B()
on ( ( all A() ) -> B() )
The following table illustrates how the execution of on all A() -> B() proceeds over time as the pattern of input events is processed by the correlator. The timeline is from left to right, and each stage is labeled with a time tn, where tn+1 occurs after tn. To the left are listed the event listeners, and next to each one (after the ?) is shown what event template that event listener is looking for at that point in time. In the example, assuming L was the initial event listener, L', L'' and L''' are other sub-event-listeners that are created as a result of the all operator.
Guide to the symbols used:
This symbol
indicates the following
A specific point in time when a particular event is received.
No match was found at that time.
The listener has successfully located an event that matches its current active template.
A listener has successfully triggered.
A new listener is going to be created.
The parent event listener denoted by on all A() -> B() will never terminate as there will always be a sub-event-listener active that is looking for an A.
Evaluating event listeners for an A-event followed by all B-events
Consider an event listener defined as follows:
on A() -> all B() success();
The monitor would now match on all the patterns consisting of the first A and each possible following B.
For clarity this is the same as:
on ( A() -> ( all B() ) ) success();
The way this works is that the correlator creates a second event listener after finding a matching B. The second event listener watches for the next B, and so on repeatedly until the monitor is explicitly killed.
Therefore on A() -> all B() would match {A1, B1}, {A1, B2} and {A1, B3}.
Graphically this would now look as follows:
The table shows the early states of L' and L'' in light color because those event listeners actually never really went through those states themselves. However, since they were created as a clone of another event listener, it is as though they were.
The parent event listener denoted by on (A() -> all B()) will never terminate as there will always be a sub-event-listener looking for a B.
Evaluating event listeners for all A-events followed by all B-events
Consider the following event listener definition:
on all A`()` -> all B`()` success();
or
on ( ( all A`()` ) -> ( all B`()` ) ) success();
Now the monitor would match on an A and create another event listener to look for further As. Each of these event listeners will go on to search for a B after it encounters an A. However, in this instance all event listeners are duplicated once more after matching against a B.
The effect of this would be that on all A -> all B would match {A1, B1}, {A1, B2}, {A1, B3}, {A2, B1}, {A2, B2}, {A2, B3} and {A3, B3}. That is, all the possible permutations. This could cause a very large number of sub-event-listeners to be created.
Info
The all operator must be used with caution as it can create a very large number of sub-event-listeners, all looking for concurrent patterns. This is particularly applicable if multiple all operators are nested within each other. This can have an adverse impact on performance.
Now consider the example,
on all ( A() -> all B() ) success();
This will match the first A followed by all subsequent Bs. However, as on every match of an A followed by B, ( A() -> all B() ) becomes true, then a new search for the “next” A followed by all subsequent Bs will start. This will repeat itself recursively, and eventually there could be several concurrent sub-event-listeners that might match on the same patterns, thus causing duplicate triggering.
Thus matching against {A1, B1}, {A1, B2}, {A1, B3}, and twice against {A3, B3}. Notice how the number of active event listeners is progressively increasing, until after t12 there would actually be six active event listeners, three looking for a B and three looking for an A.
Defining event listeners with temporal constraints
So far this section has shown how to use event expressions to define interesting patterns of events to look for, where the events of interest depend not only on their type and content, but also on their temporal relationship to (whether they occur before or after) other events.
Being able to define temporal relationships can be useful, but typically it also needs to be constrained over some temporal interval.
Listening for event patterns within a set time
Consider this earlier example:
event StockTick {
string name;
float price;
}
event NewsItem {
string subject;
string newsHeading;
}
monitor NewsSharePriceSequence_ACME {
// Look for a NewsItem followed by a StockTick
//
action onload() {
on NewsItem("ACME",*) -> StockTick("ACME",*)
notifyUser();
}
// Print a message, event sequence detected
//
action notifyUser() {
log "Event sequence detected.";
}
}
This will look for the event pattern of a news item about a company followed by a stock price tick about that company. Once improved this could be used to detect the beginning of a rise (or fall) in the value of shares of a company following the release of a relevant news headline.
However, unless a temporal constraint is put in place, the monitor is not going to be that pertinent, as it might trigger on an event pattern where the price change occurs weeks after the news item. That would clearly not be so useful to a trader, as the two events were most likely unrelated and hence not indicative of a possible trend.
If the event listener above is rewritten as follows,
on NewsItem("ACME",*) -> StockTick("ACME",*) within(30.0)
notifyUser();
the StockTick event would now need to occur within 30 seconds of NewsItem for the event listener to trigger.
The within(float) operator is a postfix unary operator that can be applied to an event template (the StockTick event template in the above example). Think of it like a stopwatch. The clock starts ticking as soon as the event listener starts looking for the event template that the within operator is attached to. If the stopwatch reaches the specified figure before the event template evaluates to true, then the event template becomes permanently false.
In the above code, the timer is only activated once a suitable NewsItem is encountered. Unless an adequate StockTick then occurs within 30 seconds and makes the expression evaluate to true, the timer will fire and fail the whole event listener.
You can apply the within operator to any event template. For example:
on A() within(10.0) listenerAction();
After the correlator sets up this event listener, the event listener must detect an A event within 10 seconds. If no A event is detected within 10 seconds, the event expression becomes permanently false and the correlator subsequently terminates the event listener.
Waiting within an event listener
The second timer operator available for use within event expressions is wait(float).
The wait operator lets you insert a ’temporal pause’ within an event expression. Once activated, a wait expression becomes true automatically once the specified amount of time passes. For example:
on A() -> wait(10.0) -> C() success();
Execution of this event listener proceeds as follows:
Set up an event template to watch for an A event.
After detecting an A event, wait 10 seconds. Set up an event template to watch for a C event.
In addition to being part of an event expression, wait can also be used on its own.
on wait(20.0) success();
When the correlator instantiates this event listener the event listener just waits for the number of seconds specified (here being 20), then it evaluates to true, triggers, and causes the correlator to execute the success() action.
Therefore a wait clause starts off being false, and then turns to true once its time period expires. This behavior can be inverted through use of not. The expression not wait (20.0) would start off being true, and stay true for 20 seconds before becoming false.
Consider the following example:
on B() and not wait(20.0) success();
This event listener triggers only if a B event is detected within 20 seconds after the correlator sets up the event template that watches for B events. After 20 seconds, the not wait(20.0) clause would become false and prevent the event listener from ever triggering. This would therefore be the same as
on B within(20.0) success();
By using all with wait, you can easily implement a periodic repeating timer,
on all wait(5.0) success();
This event listener triggers every 5 seconds and causes the correlator to execute the success() action each time.
The at temporal operator lets you express temporal activity with regards to absolute time. The at operator allows triggering of a timer:
At a specific time, for example, 12:30pm on the 5th April.
Repeatedly with regards to the calendar when used in conjunction with the all operator, across seconds, minutes, hours, days of the week, days of the month, and months, for example, on every hour, or on the first day of the month, or every 10 minutes past the hour and every 40 minutes past the hour.
Info
Important:
Triggering using the at operator either uses a specified time zone or the time zone in which the correlator is running. If the time zone contains time changes (for example, Daylight Saving Time), then a listener which would trigger during a period of time which is skipped (when the clocks go forward) will not trigger, since that time did not occur. Listeners which would trigger during a repeated section of time (when the clocks go back) will trigger for both the first time and the second time, since that time occurred twice. This is true for all patterns using the at operator.
The at operator can be embedded within an event expression in a manner similar to the wait operator. If used outside the scope of an all operator it will trigger only once, at the next valid time as expressed within its elements. In conjunction with an all operator, it will trigger at every valid time.
The wildcard symbol (*) can be specified to indicate that all values are valid, for example:
on at(5, *, *, *, *) success();
would trigger at the next “five minutes past the hour”, while
on all at(5, *, *, *, *) success();
would trigger at five minutes past each hour (that is, every day, every month).
Whereas,
on all at(5, 9, *, *, *) success();
would trigger at 9:05am every day. However,
on all at(5, 9, *, *, 1) success();
would trigger at 9:05am only on Mondays, and never on any other week day. This is because the effect of the wildcard operator is different when applied to the days_of_week and the days_of_month operands. This is due to the fact that both specify the same entity. The rule is therefore as follows:
As long as both elements are set to wildcard, then each day is valid.
If either of the days_of_week or the days_of_month operand is not a wildcard, then only the days that match that element will be valid. The wildcard in the other element is effectively ignored.
If both the days_of_week and the days_of_month operands are not wildcards, then the days valid will be the days which match either. That is, the two criteria are ‘or’ ’ed, not ‘and’ ’ed.
A range operator (:) can be used with each element to define a range of valid values. For example:
on all at(5:15, *, *, *, *) success();
would trigger every minute from 5 minutes past the hour till 15 minutes past the hour.
A divisor operator (integer, x) can be used to specify that every x’th value is valid. Therefore
on all at(*/10, *, *, *, *) success();
would trigger every ten minutes, that is, at 0, 10, 20, 30, 40 and 50 minutes past every hour.
If you wish to specify a combination of the above operators you must enclose the element in square braces ([ ]), and separate the value definitions with a comma (,). For example
on all at([*/10,30:35,22], *, *, *, *) success();
indicates the following values for minutes to trigger on; 0,10, 20, 30, 40 and 50, from 30 to 35, and specifically the value 22.
A further example,
on all at(*/30,9:17,[*/2,1],*,*) success();
would trigger every 30 minutes from 9am to 5pm on even numbered days of the month as well as specifically the first day of the month.
Specifying the time zone
By default, at() operators use the time zone in which the correlator is running. You can override the time zone for an individual listener by providing the optional time_zone parameter as the final operand. If this is the empty string (""), then it also uses the time zone in which the correlator is running. Otherwise, it should be one of the valid time zone names listed in Supported time zones. We recommend that you only use time zones in “Area/City” format (for example, “Europe/London”). For convenience, we have also provided constants corresponding to each of these time zones, which you can find in the TimeZone namespace in the API reference for EPL (ApamaDoc). The “Area” part is an event with the “City” part being a constant on that event type. So to use “Europe/London”, write TimeZone.EUROPE.LONDON instead.
For example:
on all at(5, 9, *, *, *, Timezone.AMERICA.NEW_YORK) {... }
This would be executed at 9:05 AM in the “America/New_York” time zone, independent of the time zone in which the correlator is running.
In addition to the location constants, there are two special constants:
TimeZone.ETC.SYSTEM - The time zone in which the correlator is running.
TimeZone.ETC.UTC - UTC.
Using variables to specify times
If you wish to programmatically parameterize usage of the at operator, you have to use variables in conjunction with it. You can replace any of the parameters to the at operator with a string variable or with a sequence of integer variables.
The first alternative, using a string variable, allows you to define the matching criteria within a string variable and then specify the variable within the at call. The following example shows how this can be done. Each of the parameters can be replaced with a string variable in this way.
string minutes = "*/30";
on all at(minutes,9:17,[*/2,1],*,*) success();
The other alternative is to use a sequence of integer variable. This is only useful when you want to specify a selection of valid values for the parameter.
sequence<integer> days = new sequence<integer>;
days.append(1); // Monday is ok
days.append(3); // and so is Wednesday
on all at(*,*,*,*,days) success();
When the correlator receives an event, it gives the event a timestamp that indicates the time that the correlator received the event. The correlator then places the event on the input queue of each public context. The correlator processes events in the order in which they appear on input queues.
An input queue can grow considerably. In extreme cases, this might mean that a few seconds pass between the time an event arrives and the time the correlator processes it. As you can imagine, this has implications for whether the correlator triggers listeners. However, the correlator uses event timestamps, and not real time, to determine when to trigger listeners.
As an extreme example, suppose a monitor is looking for A -> B within(2.0). The correlator receives event A. However, the queue has grown to a huge size and the correlator processes event A three seconds after event A arrives. The correlator receives event B one second after it receives event A. Some events in the queue before event B cause a lot of computation in the correlator. The result is that the correlator processes event B five seconds after event B arrives. In short, event B arrives one second after event A, but the correlator processes event B three seconds after it processes event A.
If the correlator used real time, A -> B within(2.0) would not be triggered by this pattern. This is because the correlator processes event B more than two seconds after processing event A. However, the correlator uses the timestamp to determine whether to trigger actions. Consequently, A -> B within(2.0) does trigger, because the correlator received event B one second after event A, and so their timestamps are within 2 seconds of each other.
As you can see, the number of events on an input queue never affects temporal comparisons.
Event arrival time
As mentioned before, when an event arrives, the correlator assigns a timestamp to the event. The timestamp indicates the time that the event arrived at the correlator. If you coassign an event to a variable, the correlator sets the timestamp of the event to the current time in the context in which the coassignment occurs.
The correlator uses clock ticks to specify the value of each timestamp. The correlator generates a clock tick every tenth of a second. The value of an event’s timestamp is the value of the last clock tick before the event arrived.
When you start the correlator, you can specify the –frequency hz option if you want the correlator to generate clock ticks at an interval other than every tenth of a second. Instead, the correlator generates clock ticks at a frequency of hz per second. Be aware that there is no value in increasing hz above the rate at which your operating system can generate its own clock ticks internally. On UNIX and some Windows machines, this is 100 Hz and on other Windows machines it is 64 Hz.
In an event expression, when you specify the within, wait, or at operator you are specifying a timer. Every timer has a trigger time. The trigger time is when you want the timer to fire.
When you use the within operator, the trigger time is when the specified length of time elapses. If a within timer fires, the event listener fails. In the following event listener, the trigger time is 30 seconds after A becomes true.
on A -> B within(30.0) notifyUser();
If B becomes true within 30 seconds after the event listener detects an A, the trigger time is not reached, the timer does not fire, and the monitor calls the notifyUser() action. If B does not become true within 30 seconds after the event listener detects an A, the trigger time is reached, the timer fires, and the event listener fails. The monitor does not call notifyUser(). The correlator subsequently terminates the event listener since it can never trigger.
When you use the wait operator, the trigger time is when the specified pause during processing of the event expression has elapsed. When a wait timer fires, processing continues. In the following expression, the trigger time is 20 seconds after A becomes true. When the trigger time is reached, the timer fires. The event listener then starts watching for B. When B is true, the monitor calls the success action.
on A -> wait(20.0) -> B success();
When you use the at operator, the trigger time is one or more specific times. An at timer fires at the specified times. In the following expression, the trigger time is five minutes past each hour every day. This timer fires 24 times each day. When the timer fires, the monitor calls the success action.
on all at(5, *, *, *, *) success();
Info
**Important:**
Triggering using the `at` operator uses the specified time zone, or the time zone in which the correlator is running if none is specified.
At each clock tick, the correlator evaluates each timer to determine whether that timer’s trigger time has been reached. If a timer’s trigger time has been reached, the correlator fires that timer. When a timer’s trigger time is exactly at the same time as a clock tick, the timer fires at its exact trigger time. When a timer’s trigger time is not exactly at the same time as a clock tick, the timer fires at the next clock tick. This means that if a timer’s trigger time is.01 seconds after a clock tick, that timer does not fire until.09 seconds later.
When a timer fires, the current time is always the trigger time of the timer. This is regardless of whether the timer fired at its trigger time or at the first clock tick after its trigger time. In other words, the current time is equal to the value of the currentTime variable when the timer was started plus the elapsed wait time. For example:
float listenerSetupTime := currentTime;
on wait(1.23) {
//When the timer fires, currentTime = (listenerSetupTime + 1.23)
}
A single clock tick can make a repeating timer fire multiple times. For example, if you specify on all wait(0.01), this timer fires 10 times every tenth of a second.
Because of rounding constraints,
A timer such as on all wait(0.1) drifts away from firing every tenth of a second. The drift is of the order of milliseconds per century, but you can notice the drift if you convert the value of the currentTime variable to a string.
Two timers that you might expect to fire at the same instant might fire at different, though very close, times.
The rounding constraint is that you cannot accurately express 0.1 seconds as a float because you cannot represent it in binary notation. For example, the on wait(0.1) event listener waits for 0.10000000000000000555 seconds.
To specify a timer that fires exactly 10 times per second, calculate the length of time to wait by using a method that does not accumulate rounding errors. For example, calculate a whole part and a fractional part:
monitor TenTimesPerSecondMonitor {
// Use integers to keep track of the next timer fire time.
// This ensures that the value of the currentTime variable increases
// by exactly 1.0 after every 10 tenths of a second.
integer nextFireTimeInteger;
integer nextFireTimeFraction;
action onload() {
nextFireTimeInteger := currentTime.ceil();
nextFireTimeFraction := (10.0 *
(currentTime-nextFireTimeInteger.toFloat() ) ).ceil();
setupTimeListener();
}
action setupTimeListener() {
nextFireTimeFraction := nextFireTimeFraction + 1;
if(nextFireTimeFraction = 10) {
nextFireTimeFraction := 0;
nextFireTimeInteger := nextFireTimeInteger+1;
}
on wait( (nextFireTimeInteger.toFloat() +
(nextFireTimeFraction.toFloat()/10.0) ) - currentTime )
{
setupTimeListener();
doWork();
}
}
action doWork()
{
// This is called 10 times every second.
log currentTime.toString();
//...
}
}
When a timer fires, the correlator processes items in the following order. The correlator:
Triggers all event listeners that trigger at the same time.
Routes any events, and routes any events that those events route, and so on.
Fires any timers at the next trigger time.
Disabling the correlator’s internal clock
By default, the correlator keeps time by generating clock ticks every tenth of a second. If you specify the –Xclock option when you start a correlator, the correlator disables its internal clock. This means the correlator does not generate clock ticks and does not assign timestamps based on clock ticks to incoming events.
Instead, it is up to you to send &TIME events into the correlator to externally keep time. This gives you the ability to artificially control how the correlator keeps time.
Time flows in all contexts, including private contexts. Also, different contexts can have different internal times. This happens when one context is still processing events that arrived at an earlier time while another is processing more recent events. The “currentTime” is always the time of the events being processed. (As opposed to wall-clock time, which can be obtained from the Time Manager EPL plug-in.)
Externally generating events that keep time (&TIME events)
A &TIME event can have one of the following formats:
It can contain a number of seconds:
&TIME(float *seconds*)
The seconds parameter represents the number of seconds since the epoch, 1st January 1970. The maximum value for seconds that the correlator can accept is 1012, which equates to roughly 33658 AD, and should be enough for anyone. However, most time formatting libraries cannot produce a date for numbers anywhere near that large.
Or it can contain a time string:
&TIME(string *time*)
The time is a string in extended ISO8601 form, with fractional seconds. For example:
&TIME("2015-04-20T23:32:41.032+01:00")
&TIME("2015-04-20T22:32:41.032+00:00")
&TIME("2015-04-20T22:32:41.032Z")
&TIME("2015-04-20T22:32:41.032")
These all refer to the same time. Note that the first example shows the time in a different time zone with an offset of 1 hour.
When the correlator processes an &TIME event by taking it off an input queue, the correlator sets its internal time (the current time) in that context to the value encoded in the event. Every event that the correlator processes after an &TIME event and before the next &TIME event has the same timestamp. That is, they have the timestamp indicated by the value of the previous &TIME event. For example:
&TIME(1)
A()
B()
&TIME(2)
C()
Events A and B each have a timestamp of 1. Event C has a timestamp of 2.
If you specify the -Xclock option, and you do not send &TIME events to the correlator, it is as if time has stopped in the correlator. Every event receives the exact same timestamp. While not sending time events is not strictly incorrect, it does mean that time stands still.
You must use great care when implementing this facility. There are EPL operations that rely on correct time-keeping. For example, all timer operations rely on time progressing forwards. Timers will fail to fire if time remains at a standstill, or worse, moves backwards. There is a warning message in the correlator log if you send a time event that moves time backwards.
When sending &TIME events into a multi-context application, the time ticks are delivered directly to all contexts. This can be different than the way in which events in the .evt file are sent in to the correlator and then sent between contexts in an application. This difference can result in processing events at an incorrect simulated time. In these cases, sending &FLUSHING(1), for example, before sending time ticks and events can result in more predictable and reliable behavior.
You are not required to send &TIME events every tenth of a second. You can send them at larger intervals and timers will behave as they would when the correlator generates clock ticks. For a repeating timer, a single &TIME event can make it fire multiple times. Consequently, sending an &TIME event can have a lot of overhead if it is a large time jump and there are repeating timers. For example, consider the following pattern:
You start the correlator and specify the -Xclock option, which sets the time to 0.
You inject a timer into the correlator, for example, on all wait(0.1).
You send an &TIME event to the correlator and this event has a relatively large value, for example, 1185898806.
The result of this pattern is that the timer fires many times because the &TIME event causes each intermediate, repeating timer to fire. (Intermediate timers are timers that are set to fire between the last-received time and the next-received time.) For the example given, the timer fires 1010 times, which can take a while to process. You can avoid this problem by doing any one of the following:
Send the correlator an &TIME event and specify a sensible time before you set up any timers. This is likely to be your best alternative.
Send the correlator an &TIME event and specify a sensible time before you inject any monitors.
Setting the time in the correlator (&SETTIME event)
A &SETTIME event can have one of the following formats:
It can contain a number of seconds:
&SETTIME(float *seconds*)
The seconds parameter represents the number of seconds since the epoch, 1st January 1970. For example:
&SETTIME(0) sets the time to “Thu Jan 1 00:00:00.0 BST 1970”.
&SETTIME(1185874846.3) sets the time to “Tue Jul 31 09:40:46.3 BST 2007”.
Or it can contain a time string:
&SETTIME(string *time*)
The time is a string in extended ISO8601 form, with fractional seconds. For example:
&SETTIME("2015-04-20T23:32:41.032+01:00")
&SETTIME("2015-04-20T22:32:41.032+00:00")
&SETTIME("2015-04-20T22:32:41.032Z")
&SETTIME("2015-04-20T22:32:41.032")
These all refer to the same time. Note that the first example shows the time in a different time zone with an offset of 1 hour.
Normally, you do not need to send &SETTIME events. You would just send &TIME events. An &SETTIME event is useful only to avoid the problem pattern described above. The only difference between an &SETTIME event and an &TIME event is that the &SETTIME event causes an intermediate, repeating timer to fire only once while the &TIME event causes intermediate, repeating timers to fire repeatedly. For example, on all wait(0.1) fires ten times for every second in the difference between consecutive &TIME events. However, it fires only once when the correlator receives an &SETTIME event.
If you decide to send an &SETTIME event before an &TIME event, you typically want to send the &SETTIME event only before the first &TIME event. You should not send an &SETTIME event before subsequent &TIME events. Doing so causes a jumpy quality in the behavior of time. There is a warning message in the correlator log if you set a time that moves time backwards.
Apama applications running in the correlator can make use of Apama out of band notifications. Out of band notifications are events that are automatically sent to all public contexts in a correlator whenever any component (an IAF adapter, dashboard, another correlator, or a client built using the Apama SDKs) connects or disconnects from the correlator.
For example, consider an environment where correlator A and correlator B both have out of band notifications enabled and are connected so that events from correlator A are sent to correlator B. In this case, correlator A will receive a ReceiverConnected event and correlator B will receive a SenderConnected event. The Apama application running in correlator A and B can listen for those events and execute some application logic. Note that clients such as dashboards and IAF adapters typically connect as both receiver and a sender together and, therefore, two events would be sent in quick succession.
Out of band events are defined in the com.apama.oob package and consist of:
OutOfBandConnections
ReceiverConnected
SenderConnected
ReceiverDisconnected
SenderDisconnected
OutOfBandConnections contains helper functions to get currently connected senders and receivers synchronously. These functions return a sequence of ReceiverConnected events for connected receivers and sequence of SenderConnected events for connected senders. Your application can call these functions at any time and can consume the ReceiverConnected and SenderConnected events in the same way as it consumes asynchronous out of band events. This is particularly useful for getting information about connected senders and receivers which were already connected before the application was injected and whose ReceiverConnected and SenderConnected events were missed by the application. See the API reference for EPL (ApamaDoc) for more information about the event and helper functions provided.
The ReceiverConnected and SenderConnected events contain the name of the component that is connecting. When correlators and IAF adapters send a notification event, the format of the string that contains the component name is as follows:
"*name*"
If no name is provided, however, the component name is as follows:
"name (on port port_number)"
The name is the name that was specified when the component was started. For correlators and IAF adapters, you can specify a name with the --name option when you start the component (see Starting the correlator and IAF command-line options). The name defaults to correlator or iaf according to the type of component. The port_number is the port that the connecting receiver or sender is running on.
Out of band events make it possible for developers of Apama components to add appropriate actions for the component to take when it receives notice that another component of interest has connected or disconnected. For example, an adapter can cancel outstanding orders or send a notification to an external system.
To enable out of band notifications in your Apama applications, you add the Out of Band Event Notifications bundle to your project. This bundle contains the event definitions and the monitor that enables the notifications. See Adding bundles to projects or Creating and managing an Apama project from the command line for further information. In your Apama application, you have to create a listener for out of band events specific to the components in which you are interested.
Info
You can also enable out of band notifications for a correlator with the engine_management tool and its -r setOOB on option. Be sure to inject the event definitions before running the tool with that option. For more information about using the engine_management tool, see Shutting down and managing components.