-
Notifications
You must be signed in to change notification settings - Fork 91
MiniTutorial
Assuming Calvin has been installed and works as expected (See installation instructions), create a file named hello.calvin
, and enter the following:
/* Actors */
src : std.Trigger(tick=1.0, data="Hello, world")
snk : io.Log(loglevel="INFO")
/* Connections */
src.data > snk.data
This is an example of CalvinScript which is how an application is described in Calvin. There are two main parts to the script; First, the declaration of actors used in the application, and then a description of how they are connected. In this simple example, there are two actors, called src
and snk
. The first actor, src
is of type std.Trigger()
with the sole purpose of generating a tick to the system. To this purpose, it takes two parameters, tick
, which is the interval, in seconds, with which to generate the data, and data
which is what to output on each tick. The output is then sent on the data
port of the actor.
The snk
actor is of type io.Log()
, it has a single parameter, loglevel, and outputs whatever data it gets on its data
port to the runtime log.
The documentation of these types can be easily found using the csdocs
command:
$ csdocs std.Trigger
std.Trigger(tick, data): Pass on given _data_ every _tick_ seconds
Outputs: data
$ csdocs io.Log
io.Log(loglevel): Write data to calvin log using specified loglevel.
Inputs: data
All valid actors can be inspected using csdocs, and they should all have some form of documentation.
Calvin applications are usually deployed to an existing runtime together with the necessary deployment information, but for small examples such as this one, there is a short cut. Excute the following from the same directory as the file is located in:
$ csruntime --host localhost hello.calvin
After a lot of INFO
messages listing some information of what Calvin has found in the system w.r.t capabilities, the following should appear (the application id and number of lines will differ slightly):
Deployed application 922a2096-bfd9-48c8-a5a4-ee900a180ca4
2016-07-11 08:20:34,667 INFO 11202-calvin.Log: Hello, world
2016-07-11 08:20:35,667 INFO 11202-calvin.Log: Hello, world
and then Calvin exits. When starting Calvin this way, with a script, there is a default timeout of 3 seconds before exiting. Normally, Calvin should not exit, but when toying around with small scripts, this may be the preferred way of doing it. The timeout can be changed:
$ csruntime --host localhost -w 10 hello.calvin
will run for 10 seconds before exiting. Use -w 0
or --keepalive
to run forever, but as was previously mentioned, this is not normally how a Calvin application is deployed.
The --host
argument is important. For sample scripts, such as this, which only spans a single runtime localhost
suffices, but when running a system with several runtimes, it is important that this argument is the IP(v4) address of the host running Calvin.
We will now have a look at how to make a distributed Calvin application. Create a text file named hello.deployjson
and enter (or copy & paste) the following:
{
"requirements": {
"src":[{"op":"node_attr_match","kwargs":{"index":["node_name",{"name":"runtime-0"}]},"type":"+"}],
"snk":[{"op":"node_attr_match","kwargs":{"index":["node_name",{"name":"runtime-1"}]},"type":"+"}]
}
}
Then, start two Calvin runtimes, either on the same computer, or on different computers (just make sure they can reach each other via IP)
$ csruntime --host 192.168.2.3 --port 5000 --controlport 5001 --name runtime-0 &
$ csruntime --host 192.168.2.3 --port 5002 --controlport 5003 --name runtime-1 &
Of course, you need to replace 192.168.2.3
with the IP of your computer. The --port
parameter sets the port Calvin will use for its internal runtime to runtime communication, and --controlport
is the port used for accessing the control api, which we will use to deploy the application. The internal communication uses a Calvin-specific protocol, whereas the control api is based on REST over http.
You can also use the script setup_system.sh
in extras/docker
to setup this same system:
$ ./setup_system.sh -e 192.168.2.3 -r 1 -n dht
Now, it is time to deploy the application with the deployment requirements we created earlier. The utility cscontrol
is a shortcut to the control api. We can deploy an application with it like this:
$ cscontrol http://192.168.2.3:5001 deploy --reqs hello.deployjson hello.calvin
This will deploy the application, with the src
actor being placed on runtime-0 and the snk
actor being placed on runtime-1. When starting csruntime
from the command line, the output will appear in the console, when using the setup_system.sh
command, it is logged to a file corresponding to the name of the runtime, i.e. runtime-0.log
and runtime-1.log
in this case.
For further details about how to configure your system when using multiple runtimes, see Configuration section, particularly the parts about the registry.
CalvinScript allows you to group actors together in a component, which can then be used as a short cut when building scripts. For example, using the logging actor we used earlier, say we want to prepend "mylog:" to each logging entry. The script, amended to include this, would be:
/* Actors */
src : std.Trigger(tick=1.0, data="Hello, world")
prefix: text.PrefixString(prefix="mylog:")
snk : io.Log(loglevel="INFO")
/* Connections */
src.data > prefix.in
prefix.out > snk.data
Then, later, you may find you want that same logging prefix, but on a logger with loglevel "WARNING". Rather than repeating the code, let us wrap it in a component, and parameterize the name and loglevel:
component MyLog(logname, loglevel) data -> {
prefix: text.PrefixString(prefix=logname)
snk : io.Log(loglevel=loglevel)
.data > prefix.in
prefix.out > snk.data
}
This defines a component named MyLog
with two parameters logname
and loglevel
, one inport data
, and no outports. Note how the ports of a component are used (prefixed with .
but no actorname.) This component can now be used (almost) as were it an actor.
/* Actors */
src : std.Trigger(tick=1.0, data="Hello, world")
infolog : MyLog(logname="myinfolog:", loglevel="INFO")
warnlog : MyLog(logname="mywarnlog:", loglevel="WARNING")
/* Connections */
src.data > infolog.data
src.data > warnlog.data
With the currently quite limited (but growing!) selection of actors available, it is inevitable that a calvin developer will eventually end up with a problem which cannot be solved with existing actors, nor with components built from them. Consequently, writing a new actor, from scratch, is necessary. Below is a simple example, see the wiki entry on Actors for more.
Say you have an application where you need to divide two numbers. A straightforward problem which, likely requires two in ports with the numbers, and one output with the result.
from calvin.actor.actor import Actor, condition
class InputDiv(Actor):
"""
Divides input on port 'dividend' with input on port 'divisor'
Inputs :
dividend : integer
divisor : integer
Output :
result
"""
def init(self):
pass
@condition(action_input=['dividend', 'divisor'], action_output=['result'])
def divide(self, numerator, denumerator):
result = numerator / denumerator
return (result,)
action_priority = (divide,)
To try it out , create a directory tree as follows and save it there:
actors/
math/
__init__.py
InputDiv.py
There should be a tool for this, of course.
By default, calvin only uses pre-installed actors. In order to use this new one, create a file calvin.conf
with the contents:
{
"global": {
"actor_paths": ["./actors"]
}
}
This tells calvin to look for additional actors in the ./actors
directory.
In the same directory, create a script mathtest.calvin
, with
ten : std.Constant(data=10)
five : std.Constant(data=5)
div : math.InputDiv()
out : io.Print()
ten.token > div.dividend
five.token > div.divisor
div.result > out.token
(You will note that this is a rather inefficient way of dividing two numbers.)
The directory structure should now be:
calvin.conf
mathtest.calvin
actors/
math/
InputDiv.py
Run the application with
csruntime --host localhost mathtest.calvin
Among the output should be the line
2
which is correct.
But what happens if the divisor is 0? (It will make the application crash.) In order to handle it somewhat gracefully, we add a second action to the actor which checks the input on the ports, and forwards an exception token if the divisor is 0. Edit the file InputDiv.py
(or copy & paste) to contain the following:
from calvin.actor.actor import Actor, condition
from calvin.runtime.north.calvin_token import ExceptionToken
class InputDiv(Actor):
"""
Divides input on port 'dividend' with input on port 'divisor'
Inputs :
dividend : integer
divisor : integer
Output :
result
"""
def init(self):
pass
@condition(action_input=['dividend', 'divisor'], action_output=['result'])
def divide(self, numerator, denumerator):
if denumerator != 0:
result = numerator / denumerator
else:
result = ExceptionToken("Division by 0")
return (result,)
action_priority = (divide,)
Changing the script mathtest.calvin
to
ten : std.Constant(data=10)
zero : std.Constant(data=0)
div : math.InputDiv()
out : io.Print()
ten.token > div.dividend
zero.token > div.divisor
div.result > out.token
will give the log entry
<ExceptionToken> Division by 0
which is far better than a crash.