Apama applications that run in the correlator are usually written using EPL, which is Apama’s native event processing language. This blog post will give you a short introduction into the basics of EPL, covering some basic concepts and ideas. It is assumed the reader already has some knowledge of other programming languages (C/C++, Java, Python, etc) and understands basic programming concepts.
Events
Before EPL can look for patterns in event streams, you must define the types of events you are interested in. An event definition describes to the correlator the composition of an event type, and the type and name of fields it contains. An example of an event definition for a temperature reading is:
event Temperature {
string sensorId;
float value;
}
The type informs the correlator how to handle that field and what operations to allow on it. As you can see, the correlator can handle multiple types, such as numeric values and textual values, within the same event type. Apama can handle any number of different event types at one time.
External event sources such as connectivity plug-ins, clients and the IAF need to be able to send events into the correlator. For the correlator to be able to detect an event of interest, the event’s type definition must have been injected into the correlator. An example of a Temperature
event is as follows:
Temperature ( "1234", 26.87 )
Types
Apama supports these primitive types: boolean
, decimal
, float
, integer
and string
. Technically, a string is a reference type, but it is immutable and behaves more like a primitive than a reference type. These types store data and allow operations as you would expect with any programming language.
EPL also provides a number of object types. These types are manipulated by reference as opposed to by value (in the same way as complex types are handled in Java). The basic reference types are sequence
, dictionary
and event
.
A sequence
can be thought of as an array or list of some Apama type. A dictionary
is a map from a comparable, unique key to a value of some Apama type. Some examples of the syntax are:
// A sequence to store a list of temperature events
sequence< Temperature > myTemps;
// A dictionary identifiers to names
dictionary < integer, string > idToName;
An event
is a data object that contains other data values, akin to a class
in C++/Java. It can represent notifications of something happening, or just a container of related data values. Events are typically defined by the user and they can be used as types in EPL. Events describe the individual data items in a stream that is sent into the correlator.
All of the Apama types offer the basic operators you would expect –
- maths operators for
integer
,float
anddecimal
- concatenation, searching and regex for
string
- insertion and getters for
sequences
anddictionaries
.
All types also offer a toString()
action which will convert that type to a string, very useful for logging. See the full Apama documentation for all available operators.
Actions
Actions are the EPL equivalent of methods or functions in other programming languages. They are segments of code that can be accessed and ‘called’ from other parts of the code. They can take parameters and return a result although both are optional. The format for defining various action types are:
// No params and no return value
action actionName() {
// do something
}
// Params and a return value
action actionName( type1 param1, type2, param2, ...) returns type3 {
// do something
return type3_instance;
}
// For example
action myAction(integer i, float f) returns string {
return "Hello";
}
An action
can either be part of a monitor or part of an event declaration.
Listeners
A listener
is the EPL mechanism that specifies the event or sequence of events that you are interested in. Conceptually, listeners sift through the streams of events that come in to the correlator and detect matching events. A listener is constructed in EPL code using the on
keyword, followed by which event type you are interested in and optionally the filtering constraints for the fields of that event. You can also optionally copy the input event into a variable (local or global) so you can access it later, and then specify a block of code for execution when this listener triggers. You can also use the all
keyword to indicate the listener should match for each event and not just terminate after the first match.
Some examples of listener construction:
// One-time temperature event with no filtering. Will only match the first Temperature event and then terminate
on Temperature() {
// do something
}
// One-time temperature event, filtering on a value above 30.0. Will only match the first Temperature event and then terminate
on Temperature(value > 30.0) {
// do something
}
// Match all temperature events with a value above 30.0.
on all Temperature(value > 30.0) {
// do something
}
// Match all temperature events with a value above 30.0 copying the input event into the highTemp variable
Temperature highTemp;
on all Temperature(value > 30.0):highTemp {
// do something with highTemp
}
These are simple listeners, but complex listeners can be created by specifying multiple event types and using logical operators ( and, or, not, xor
) or the ‘followed by’ operator (->). You can even use temporal operators ( at, wait, within
) to trigger listeners to fire within specified times. These are all documented more thoroughly in the Apama documentation and are outside the scope of this blog post.
Monitors
A monitor
is a group of related variable declarations and actions. A monitor is the basic unit of EPL program execution. To execute a monitor, you load (inject) it into the correlator. The correlator then compiles the source EPL and invokes the monitor’s onload()
action if compilation is successful. The onload()
action is the entry point of each monitor and must be present, and can be thought of as the main()
in C/C++ of each monitor. An example monitor which processes the temperature events defined above is:
monitor TemperatureProcess {
// Temperature global variable
Temperature highTemp;
// The required onload() action, the entry point of this monitor
action onload() {
on all Temperature(value > 30.0):highTemp {
processTemp();
}
}
// Action to called when listener fires. Logs a warning about temperature.
action processTemp() {
log "WARNING - Temperature above 30.0 - " +
"id = " + highTemp.id +
"value = " + highTemp.value.toString() at INFO;
}
}
When the onload()
action is executed, a listener is set up on all Temperature
events with a value above 30.0. If this listener fires (caused by a Temperature event being processed by the correlator with a temperature value above 30.0), the incoming event is copied (co-assigned) to the monitor’s global highTemp
variable and the action processTemp()
is called. You can think of this block of code as a lambda or anonymous function that is only processed when the listener is triggered. The processTemp()
action just logs a warning to the correlator log with the values from the received Temperature
event. In a real world application this action is likely to do more, such as sending another event or even starting a fan.
Logging
The correlator creates a log file when it is run which contains lots of information about the running correlator, its status, when files are injected, etc. You can also write information to this log file from within EPL code. Using the log
keyword you can then specify a string to be written to the log file (you can construct the string in the log call, see above), followed by the log level of this string using the at
keyword followed by either TRACE
, DEBUG
, INFO
, WARN
, ERROR
or CRIT
. If the log level is not specified, it defaults to the INFO
level.
This really does just scratch the surface of how powerful EPL can be, we haven’t even touched on Contexts, spawning, Channels, routing/emitting/sending events, etc. Watch out for further EPL blog posts to help extend your knowledge.
Also see the My First Apama Application video.