Changes between Initial Version and Version 1 of OMF/OMF60/0User/2OEDLDoc


Ignore:
Timestamp:
May 17, 2019, 10:42:51 AM (19 months ago)
Author:
seskar
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • OMF/OMF60/0User/2OEDLDoc

    v1 v1  
     1== The OMF Experiment Description Language (OEDL) ==
     2
     3[[TOC(heading=OMF Documentation, OMF/*, reverse, depth=1)]]
     4
     5[[TOC(heading=OMF 6.0 Documentation, OMF/OMF60/0User*, depth=4)]]
     6
     7=== Overview ===
     8
     9OEDL is a domain-specific language for the description of an experiment's execution. OEDL is based on the Ruby language, but provides its own set of experiment-oriented commands and statements. As a new OEDL user you do not need to know Ruby to write experiment description. You can get started with only a few OEDL commands and syntax. However, some general entry-level programming knowledge would be a plus.
     10
     11An OEDL experiment description is composed of 2 main parts:
     12 1. a first part where we declare the resources that we will use in the experiment, such as applications or computing nodes, and some configurations that we would like to apply to these resources.
     13 1. a second part where we define the events that we would like to re-act to during the experiment's execution, and the associated tasks we would like to execute when these events occur.
     14=== Simple examples ===
     15 * An experiment with one computing resource, on which we execute the 'hostname' command once the resource is up
     16{{{
     17# This is the 1st section, where we declare resources and configurations
     18#
     19defGroup('Actor', "my_resource_name")
     20
     21# This is the 2nd part, where we define events and associated tasks
     22#
     23onEvent(:ALL_NODES_UP) do |event|
     24  info "This is my first OEDL experiment"
     25  group('Actor').exec('/bin/hostname')
     26  after 5.seconds do
     27    Experiment.leave_memberships
     28    Experiment.done
     29  end
     30end
     31}}}
     32 * An experiment with one computing resource and one associated application resource (ping), which is started once all resources are up and installed
     33{{{
     34defProperty("a_resource", "replace-with-your-resource-name", "ID of a resource")
     35
     36defApplication('ping') do |app|
     37  app.description = 'Simple Definition for the ping-oml2 application'
     38  app.binary_path = '/usr/bin/ping-oml2'
     39  app.defProperty('target', 'Address to ping', '', {:type => :string})
     40  app.defProperty('count', 'Number of times to ping', '-c', {:type => :integer})
     41end
     42
     43defGroup('My_Pinger', property.a_resource) do |g|
     44  g.addApplication("ping") do |app|
     45    app.setProperty('target', 'mytestbed.net')
     46    app.setProperty('count', 3)
     47  end
     48end
     49
     50onEvent(:ALL_UP_AND_INSTALLED) do |event|
     51  allGroups.startApplications
     52  after 5.seconds do
     53    Experiment.leave_memberships
     54    Experiment.done
     55  end
     56end
     57}}}
     58
     59=== Syntax ===
     60 * Unless mentioned otherwise, all user-specified names used in the commands described below must be valid Ruby identifiers
     61 * Comments within an OEDL experiment description starts with #, as in Ruby
     62==== getResources ====
     63 * The '''getResources''' command retrieves the list of resources which are associated with the current slice used by the experimenter
     64 * Syntax:
     65{{{
     66list_of_resources = getResources()
     67}}}
     68 * The result of this command is an array where each element is a resource record.
     69 * Such a record is a hash describing the resource. The experiment script can then filter the list of resources based on specific keys and values in such a record (e.g. only retain the resources of type 'node')
     70 * An example of such a resource record is:
     71{{{
     72"client_id": "some_id_for_that_node",
     73"status": "ready",
     74"omf_id": "the_id_by_which_that_node_can_be_used_in_an_OEDL_script",
     75"sliver_id": "urn:publicid:IDN+instageni.foo.edu+sliver+1234",
     76"ssh_login": {
     77  "hostname": "pc1.instageni.foo.edu",
     78  "port": "12345"
     79},
     80"interfaces": {
     81  "some_id_for_that_node:if0": {
     82    "client_id": "some_id_for_that_node:if0",
     83    "sliver_id": "urn:publicid:IDN+instageni.foo.edu+sliver+1234",
     84    "mac_address": "01234567890a",
     85    "ip": [
     86      {
     87        "address": "10.10.1.1",
     88        "type": "ipv4"
     89      }
     90    ]
     91  }
     92},
     93"urn": "urn:publicid:IDN+instageni.foo.edu+sliver+1234",
     94"type": "node"
     95}}}
     96
     97==== defProperty & property & ensureProperty ====
     98 * The defProperty command defines a new experiment property that can be used throughout the entire experiment description. The value of a property may change through the course of an experiment, and may be either a string of characters, a numerical value, or a boolean value.
     99 * The property command is used to access a previously defined property, and can be used anywhere in the experiment description
     100 * The ensureProperty command first check if a property exists, if not it defines it in the same way and using the same syntax as defProperty
     101 * Syntax:
     102{{{
     103defProperty(name, default_value, description)
     104property.name
     105ensureProperty(name, default_value, description)
     106
     107- name: name of this property
     108- default_value: the default value for this property
     109- description: some text describing what this property is about
     110}}}
     111 * Usage Examples
     112{{{
     113defProperty('rate', 300, 'Bits per second sent from sender')
     114defProperty('packetSize', 1024, 'Size of packets sent from sender, in Byte')
     115
     116some_variable = property.packetSize
     117}}}
     118 * When a property is defined using the defProperty command, and if you run the experiment with an OMF6 Experiment Controller (EC), you can set the property's initial value from the command line arguments of the EC.
     119   * for example when using the EC to run an experiment named 'test.rb' which includes a definition for a property named 'my_resource'
     120{{{
     121$ omf_rc exec test.rb -- --my_resource node1.mytestbed.net
     122}}}
     123 * A property can be used in simple arithmetic (i.e. +, -, *, /) and string (i.e. +) operations
     124{{{
     125defProperty('rate1', 300, 'Bits per second sent from one app')
     126defProperty('rate2', 400, 'Bits per second sent from another app')
     127
     128total_rate = property.rate1 + property.rate2 * 3
     129}}}
     130
     131==== loadOEDL ====
     132 * The loadOEDL command fetches an additional OEDL script and loads it at this point in the current OEDL script.
     133 * This additional script can be either:
     134   * shipped as part of the OMF EC gem package, or any other gem packages
     135   * located at a given path on the local filesystem of the machine running the EC
     136   * located at a given Web URL
     137 * Optional parameters can be passed to this additional OEDL script by mean of experiment properties (see above section)
     138 * Syntax:
     139{{{
     140loadOEDL(location, optional_properties)
     141
     142- location: a URI which references the OEDL script to load
     143    The supported URI schemes are:
     144    * system:///foo/bar.rb , which loads the file located at 'foo/bar.rb' in the default Ruby path of this EC
     145    * file:///foo/bar.rb , which loads the file located at '/foo/bar.rb' on the local filesystem
     146    * http://foo.com/bar.rb , which loads the file located at the URL 'http://foo.com/bar.rb'
     147    NOTE: the 'file://' and 'system://' schemes do not support other hosts than localhost,
     148               thus the host part of these URI should be left empty
     149- optional_properties: a hash of keys/values for the optional experiment properties to set before loading the OEDL script
     150}}}
     151 * Usage Examples
     152{{{
     153loadOEDL('system:///omf_ec/some_shipped_script')
     154loadOEDL('system:///omf_ec/some_shipped_script',  { key1: 'value1', key2: 123 })
     155
     156loadOEDL('file:///home/alice/alice_extra_script.rb')
     157
     158loadOEDL('http://some.web.server.com/the_script.rb')
     159}}}
     160
     161==== defApplication ====
     162 * The defApplication command is used to declare the definition of an application that will be used throughout this experiment description. The details of this definition are provided in a declaration block following the defApplication. Once an application is declared using this command, any resource or group of resources within the experiment description may use it.
     163 * Syntax:
     164{{{
     165defApplication(app_name) do |app|
     166  app.declaration1
     167  app.declaration2
     168  ...
     169end
     170
     171- app_name: name of this application definition
     172- declarationX: some declaration about this application
     173}}}
     174 * Usage Example
     175{{{
     176defApplication('ping') do |app|
     177  app.description = "A very simple application definition for ping"
     178  app.binary_path = "/usb/bin/ping"
     179end
     180}}}
     181 * There are 3 types of declarations that can be made within the declaration block
     182   * Generic declaration:
     183{{{
     184These are declarations about various generic attributes of this application. The valid declarations here are:
     185
     186app.description = a String giving some human-readable description of this application,
     187                  e.g. what it is, what it does, how it works, etc...
     188app.binary_path = a String holding the path to the binary for that application, when it is
     189                  installed on a computing resource
     190app.map_err_to_out = a Boolean (true or false), if true then map the standard-error stream
     191                  to standard-output stream for a running instance of this application (default = false)
     192app.pkg_tarball = a String holding a URI pointing to a tarball package for this application
     193                  (which will be extracted at the root "/" on the computing resource)
     194app.pkg_ubuntu = a String giving a Ubuntu package name for this application (which will be
     195                  installed using 'apt-get install' on the computing resource)
     196app.pkg_fedora = a String giving a Fedora package name for this application (which will be
     197                  installed using 'yum install' on the computing resource)
     198
     199When reading the following declaration, the EC will instructs the Resource to download
     200the tarball located at 'http://bar.com/foo.tgz', then uncompress it relatively to the root of the
     201Resource's filesystem. Here it is assumed that this tarball contains a binary which will be placed
     202at '/usb/local/bin/foo'
     203
     204  defApplication('foo') do |app|
     205    app.description = "A very simple application definition for the foo application"
     206    app.binary_path = "/usb/local/bin/foo"
     207    app.pkg_tarball = "http://bar.com/foo.tgz"
     208  end
     209}}}
     210   * Property declaration:
     211{{{
     212These are declarations about the parameters that this application supports. These parameters can be either configured on the command line, or dynamically via the application's standard-input stream (if the application supports this). These parameters are declared using a defProperty command with a different syntax than previously described one above. This syntax is as follows:
     213app.defProperty(prop_name, description, command_line, options)
     214
     215- prop_name: name for this parameter within OEDL experiment descriptions
     216- description: some text describing what this parameter is about
     217- command_line: the prefix that is used to set this parameter when starting the application instance from the command line, this can be an nil or an empty string when no prefix is required
     218- options: a Ruby Hash with the set of options to apply to this parameter. The list of valid options are:
     219     :order (Fixnum) the appearance order on the command line, default FIFO
     220     :dynamic (Boolean) parameter can be dynammically changed, default false
     221     :type (Numeric|String|Boolean) this parameter's type
     222     :default value given by default to this parameter
     223     :value value to set for this parameter
     224     :mandatory (Boolean) this parameter is mandatory, default false
     225
     226Examples:
     227
     228  app.defProperty('target', 'Address to ping', nil, {:type => :string, :mandatory => true, :default => 'localhost'})
     229  app.defProperty('count', 'Number of times to ping', '-c', {:type => :integer})
     230}}}   
     231   * Measurement declaration:
     232{{{
     233These are declarations about the Measurement Points (MP) that this application supports, if any. When an application has been instrumented using the OML Instrumentation Library, it can generate measurement streams during its execution. When this application is used within an OEDL experiment description, we can declare the existence of these Measurement Points and selectively enables the ones we are interested in. The declaration of such a MP is done using the following defMeasurement command and associated block:
     234app.Measurement(mp_name) do |mp|
     235  mp.defMetric(metric_name, metric_type)
     236  ...
     237end
     238
     239- mp_name: the name of this measurement point, as defined by the OML-instrumented application
     240- metric_name: the name of a metric within this measurement point, as defined by the OML-instrumented application
     241- metric_type: the type of that metric, as defined by the OML-instrumented application, e.g. :string, :uint32, :double, ...
     242   the full list of supported types is at: http://oml.mytestbed.net/projects/oml/wiki/OML_generic_interface#Data-Types
     243
     244Examples:
     245
     246  app.defMeasurement('probe_statistic') do |m|
     247    m.defMetric('dest_addr', :string)
     248    m.defMetric('ttl', :uint32)
     249    m.defMetric('rtt', :double)
     250    m.defMetric('rtt_unit', :string)
     251  end
     252
     253  app.defMeasurement('video_stream_statistic') do |m|
     254    m.defMetric('frame_number', :uint64)
     255    m.defMetric('drop_rate', :uint32)
     256    m.defMetric('codec_name', :string)
     257    m.defMetric('bitrate', :unit32)
     258  end
     259}}}
     260
     261==== defGroup ====
     262 * The deGroup command defines a new group of resources that will participate to this experiment. Such a group may include one or many resources.
     263 * As a group is itself considered a resource, a group may thus include one or many other groups.
     264 * A set of configurations and/or applications to use may be associated to a group. This is optional. In such case, all members of the group will have these configurations and/or run these applications. This association is done via declaration block as in other OEDL commands.
     265 * Syntax:
     266{{{
     267defGroup(group_name, resource1_name, resource2_name, ...)
     268
     269# or with the optional block:
     270defGroup(group_name, resource1_name, resource2_name, ...) do |g|
     271  ...
     272end
     273
     274- group_name: a name for this group
     275- resourceX_name: the name of the X-th resource to include in this group
     276}}}
     277 * Within the optional declaration block, you can:
     278   * associate an application to this group of resources, by using the addApplication command
     279   * configure the parameters of that application to some values, by using the setProperty command
     280   * enable the generation and collection of measurements streams supported by the application, by using the measure command
     281   * these commands have the following syntax:
     282{{{
     283defGroup(group_name, resource1_name) do |g|
     284  g.addApplication(app_definition_name, app_definition_location) do |app|
     285    app.name = 'optional_name_prefix'
     286    app.setProperty(prop_name, "some_value")
     287    ...
     288    app.measure(mp_name, :samples => 1)
     289    ...
     290  end
     291end
     292
     293- app.name = 'optional_name_prefix'
     294   This is an *OPTIONAL* statement, which you can use to set a custom prefix for the
     295   name of this application instance. When this application will be created on the resource
     296   side, that instance will get the name: "prefix_UID", where:
     297     * prefix: is either this 'optional_name_prefix' or a default constructed one based on
     298        the application definition
     299     * UID: is a unique ID for that particular app instance on that particular resource.
     300- app_definition_name: the name of the application definition as defined by a previous
     301   defApplication declaration
     302- app_definition_location: where to find the OMF definition for this application. This is an
     303   *OPTIONAL* parameter. If not provided, the application definition must be either:
     304      * provided as part of the same script as this OEDL experiment script
     305      * loaded earlier in the same OEDL script, using the loadOEDL command
     306   This value can be either:
     307     * a path relative to the OMF packages, e.g. "system:///omf_ec/some_directory/a_app_definition_file"
     308     * a path on the local file system where the EC is running, e.g. "file:///usr/local/shared/omf/a_app_definition_file"
     309     * a web URL, e.g. "http://mytestbed.net/app_definitions/a_app_definition_file"
     310- prop_name: the name of a parameter of the application to configure to some_value, as
     311   defined by a previous defProperty within a block of a defApplication command
     312- mp_name: the name of a Measurement Point provided by the application, as defined by a
     313   previous defMeasurement within a block of a defApplication command.
     314   The measure command takes an additional parameter to specify the measurement reporting frequency,
     315   i.e. every X samples (:samples => X) or every X seconds (:interval => X)
     316
     317Examples:
     318
     319defGroup('Two_Resource', 'one_of_my_resource', 'another_of_my_resource')
     320
     321defGroup('Pinger', 'yet_another_of_my_resource') do |g|
     322  g.addApplication("ping") do |app|
     323    app.setProperty('target', 'mytestbed.net')
     324    app.setProperty('count', 3)
     325    app.measure('probe_statistic', :samples => 1)
     326  end
     327end
     328}}}   
     329  * Within the optional declaration block, you can also configure some networking parameter for this group of resources, using the following dot syntax:
     330defGroup(group_name, resource1_name) do |g|
     331  g.net.interface.attribut = some_value
     332  ...
     333end
     334
     335- interface: the OEDL shortname for a network interface, which can be either:
     336   - e0: the first ethernet interface (often eth0 on Linux resources)
     337   - e1: the second ethernet interface (often eth1 on Linux resources)
     338   - w0: the first wireless ethernet interface (often wlan0 on Linux resources)
     339   - w1: the first wireless ethernet interface (often wlan1 on Linux resources)
     340- attribut: the name of a the networking attribut to configure to some_value, which can be either:
     341   - ip: the IPv4 network address to use
     342   - type: the type of 802.11 network to use, either 'a','b','g' or 'n' (only for 802.11 wireless interface)
     343   - essid: the ESSID name to use (only for wireless interface)
     344   - mode: the wireless mode to use, either 'adhoc', 'master' (i.e. Access Point), 'managed'
     345      (i.e. Wireless Station) name to use (only for wireless interface)
     346   - channel: the channel ID to use, depending on the type of network (only for wireless interface)
     347
     348Examples:
     349
     350defGroup('Wireless_Client', 'one_of_my_resource') do |g|
     351  g.net.w0.mode = "adhoc"
     352  g.net.w0.type = 'g'
     353  g.net.w0.channel = "6"
     354  g.net.w0.essid = "example"
     355  g.net.w0.ip = "192.168.0.2/24"
     356end
     357}}}
     358
     359==== onEvent ====
     360 * OEDL adopts an event-based approach to describe the different actions required to perform during an experiment.
     361 * There are a basic set of defined events that we supports (e.g. 'ALL_NODES_UP') and users can define their own custom events (using the defEvent command)
     362 * The basic set of defined events are:
     363   * :ALL_NODES_UP this event is triggered when all the computing resources of type 'node' have confirmed their membership to all their assigned groups in this experiment
     364   * :ALL_UP_AND_INSTALLED this event is triggered when all :ALL_NODES_UP is triggered and in addition all the applications associated to all the groups have been installed (if no installation was required then the application is assumed to be installed)
     365   * :ALL_APPS_DONE, this event is triggered when all resource of type application have reported that their running instance has exited
     366   * :ALL_INTERFACE_UP, this event is triggered when all the resource of type network interface have been created and are ready to receive further commands
     367   * :ALL_UP, this event is triggered when all the resources defined in the experiment script have been created and are ready to receive further commands
     368 * The set of actions to execute when a given event has been triggered is described in a declarative block associated to that event, using the following syntax:
     369{{{
     370onEvent(:event_name, consume_event) do
     371  some_command
     372  ...
     373end
     374
     375- event_name: the name of the event to associate the enclosed actions with (e.g. :ALL_NODES_UP)
     376- consume_event: optional, by default equal to true. If set to false, then the event is not consumed, i.e. it is allowed to be triggered again in the future
     377- some_command: a command describing the actions to perform.
     378
     379Examples:
     380
     381onEvent(:ALL_NODES_UP)
     382  Experiment.done
     383end
     384}}}
     385 * Some available type of actions and their syntax are listed below:
     386    * Output some information message on the standard-output of the experiment controller
     387{{{
     388info "Hello World!"
     389}}}   
     390    * Start/stop all applications associated with all the groups
     391{{{
     392allGroups.startApplications
     393allGroups.stopApplications
     394}}}   
     395    * Start/stop all applications associated with a given group called 'Actors'
     396{{{
     397group('Actors').startApplications
     398group('Actors').stopApplications
     399}}}   
     400    * Execute another block of actions after a 10 seconds
     401{{{
     402group('Actor').startApplications
     403after 10.seconds do
     404    group('Actor').stopApplications
     405end
     406}}}   
     407    * Execute a given shell command (e.g. "/bin/hostname") on all resources within a group called 'Actors'
     408{{{
     409group('Actors').exec('/bin/hostanme')
     410}}}   
     411    * Explicitly requests all resources to leave this experiment
     412{{{
     413Experiment.leave_memberships
     414}}}   
     415    * Explicitly terminates the experiment's execution. This requires all the resources to release/terminate all their running application, then leave the experiment
     416{{{
     417Experiment.done
     418}}}   
     419    * Close the EC instance without terminating the experiment, i.e. resources will keep performing whatever tasks they are assigned to and are still part of this experiment. This would be the case in long-running experiments, where the user would close her EC instance, while letting the experiment still running, then would start a new EC instance later to interact further with the resources.
     420{{{
     421Experiment.disconnect
     422}}}
     423   
     424==== defEvent ====
     425 * As mentioned above, a user can define her own custom events to monitor within an OEDL experiment. The pattern for use this feature is as follows:
     426    * the user defines the conditions that would trigger her custom event within a defEvent block
     427    * the user then tasks to perform when that event triggers in one or many onEvent block(s)
     428{{{
     429defEvent(:MY_EVENT) do |state|
     430  # put here the conditions to check in order to trigger this event
     431end
     432...
     433onEvent(:MY_EVENT) do
     434  # put here the tasks to execute when the event :MY_EVENT has triggered
     435end
     436}}}
     437 * the conditions in the defEvent block can be based on
     438    * the state of one or many resources involved in the experiment (e.g. when the application foo has finished)
     439    * the value of any measurements collected as part of the experiment (e.g. when loss rate is above 50%)
     440    * any time conditions (e.g. at 7am, in 10 second, etc...)
     441 * these conditions are checked:
     442    * by default, only when a new message arrives to the Experiment Controller
     443    * every X second, only if the every: optional parameter is set on defEvent
     444 * the condition-checking code in the defEvent block must:
     445    * return the boolean true, when the conditions have been met
     446    * return false otherwise
     447 * Syntax:
     448{{{
     449defEvent(event_name) do |state|
     450...
     451end
     452
     453# or with the optional periodic timer:
     454
     455defEvent(event_name, every: time_duration) do |state|
     456...
     457end
     458
     459- event_name: a name for this event, we recommend using an all-capital symbol identifier, such as :MY_EVENT
     460- every: the name of the X-th resource to include in this group
     461}}}
     462 * '''Example of a custom event based on the state of some resources:'''
     463  * the EC keeps record of the states of all resources that are involved in an experiment trial, i.e. for each resource the EC maintain a set of <key,value> hash where key is a property of that resource and value is the last known value for that property
     464  * each time that the EC receives an FRCP inform message from a given resource advertising the values of all/some of its properties, the EC updates its corresponding records
     465  * thus it is possible for an experimenter to query these state records and trigger an event based on them
     466  * these state records are available within the defEvent block through the 'state' variable in the following example
     467    * the state variable is an array where each element is the record for a given resource
     468    * such an element is a hash of <key, value> as mentioned above
     469  * in the following example:
     470    * in the defEvent block, we are checking each element of the 'state' array (i.e. each record for a resource)
     471    * if that element has a 'type' key with the value 'application' (i.e. the resource is an application) and another 'state' key with the value 'stopped' (i.e. the application stopped), then we trigger the event
     472    * the corresponding onEvent defines a block of instructions to execute when the event fired. It also has its consume_event parameter set to false, which allows the event to be fired again in the future
     473{{{
     474defEvent :APP_EXITED do |state|
     475  triggered = false
     476  state.each do |resource|
     477      triggered = true if (resource.type == 'application') && (resource.state == 'stopped')
     478  end
     479  triggered
     480end
     481
     482onEvent :APP_EXITED, consume_event= false do
     483    info "An application has just finished... should we do something about it?"
     484end
     485}}}
     486 * '''Example of a custom event based on some collected measurements:'''
     487   * when an experiment starts an [http://oml-doc.orbit-lab.org OML-instrumented application] with some of its [http://oml-doc.orbit-lab.org/wiki/MeasurementPoints Measurement Points] enabled, the collected measurements are accessible to the experimenter in the defEvent block. Thus the experimenter can define test conditions on these measurements and trigger the event when these conditions are met.
     488   * the measurements are usually stored in an SQL-like database, and there are 2 ways to access these measurements:
     489     * the experimenter can directly write a specific SQL query to select the measurements she is interested in, or
     490     * she can use a [http://sequel.jeremyevans.net/ Sequel-based syntax] similar to the [wiki:/OMF/OMF60/0User/2OEDLDoc#defGraph defGraph command], to define a more user-friendly request for measurement
     491   * in both cases, the request/query is ran periodically against the measurement database
     492   * also in both cases, the results of the query are available as an array of hash:
     493     * each element of the array is a Measurement Point sample that matches the query/request, and
     494     * each <key,value> pair in that element is a metric and its corresponding value
     495   * in the following example:
     496     * we first use the Sequel-based syntax to define a request for the metrics 'oml_ts_client' and 'values' from the 'my_measurement_point' MP
     497     * we then pass this defined request as the argument to the defQuery method, which will run it against the measurement database
     498     * the value of the 'every' parameter of the 'defEvent' block is set to 1, which will tell the EC to execute the entire condition checking code in the defEvent block every 1 second
     499     * alternatively in the commented line below, we also show how to express this same measurement request using a direct SQL syntax
     500     * then if the returned array of measurement is not nil, we take the last one (e.g. pop) and compare its value against an arbitrary threshold to decide if we trigger the event
     501{{{
     502defEvent(:VALUE_ABOVE_THRESHOLD, every: 1) do
     503  # Query for some measurements...
     504  # returns an array where each element is a hash representing a row from the DB
     505  query = ms('my_measurement_point').select { [ :oml_ts_client, :value ] }
     506  data = defQuery(query)
     507
     508  # Alternatively the above line could also be:
     509  # data = defQuery('select oml_ts_client, value from my_measurement_point_table')
     510  #
     511  # Also if you want to rename 'oml_ts_client' to 'ts'
     512  # query = ms('my_measurement_point').select { [ oml_ts_client.as(:ts), :value ] }
     513  # data = defQuery('select oml_ts_client as ts, value from my_measurement_point_table')
     514
     515  triggered = false
     516
     517  if !data.nil? && !(last_row = data.pop).nil? # Make sure we have some data
     518    triggered = true if last_row[:value].abs > 0.99
     519  end
     520  triggered
     521end
     522
     523onEvent(:SINE_ABOVE_THRESHOLD) do
     524    info "The value just went above the threshold... should we do something about it?"
     525end
     526}}}
     527  * '''Example of a custom event based on a timer:'''
     528{{{
     529defEvent(:AFTER_1230, every: 1) do
     530  now = Time.now
     531  true if now.hour>12 && now.min > 30
     532end
     533
     534onEvent :AFTER_1230 do
     535  info "Do something after 12:30..."
     536end
     537}}}
     538
     539==== defGraph ====
     540  * This command is used to describe graphs, which !LabWiki will display and update during an experiment execution.
     541  * Note that these graphs will only be displayed in !LabWiki for now. If you are running an experiment directly from an OMF command line (i.e. without using !LabWiki at all), graphs will not be displayed. We plan to describe soon the graphing interface between OMF and !LabWiki, so that 3rd party may implement their own visualization tools to display defGraph-defined plots.
     542  * Graphs can only be plotted using OML-collected data from applications, which are declared and used in OEDL experiments.
     543  * There are many different types of graphs that can be plotted. The currently documented graph types are: line chart, pie chart, and histogram chart.
     544  * Syntax:
     545{{{
     546defGraph 'graph_name' do |g|
     547  g.ms('mp_name').select {[ :field_1, :field_2, ... ]}
     548  g.caption 'some_caption'
     549  g.type 'type_of_graph'
     550  ... graph_specitific_options ...
     551end
     552- graph_name, a human readble name for this graph
     553- mp_name, the name of the measurement point (from an application), which generates the measurement we would like to top
     554- field_i, the name of the specific field(s) in that measurement point, which we are interested in plotting
     555- some_caption, some caption text to add to the graph
     556- type_of_graph, the type of graph to plot for now only the following are documented: line_chart3, pie_chart2, histogram2
     557}}}
     558  * Note that in addition to the field_i defined in by the application declaration. All OML Measurement Points also defined by default addition fields, such as:
     559    * oml_sender_id, the id of the source that generated this measurement
     560    * oml_ts_client, the timestamp at the source when this measurement was generated (remapped in the collection server time reference)
     561    * oml_ts_server, the timestamp at the collection server when this measurment was received
     562  * Specific options for line chart
     563{{{
     564  g.mapping :x_axis => :field_1, :y_axis => :field_2, :group_by => :field_3
     565  g.xaxis :legend => 'some_legend_for_x_axis'
     566  g.yaxis :legend => 'some_legend_for_x_axis', :ticks => {:format => 's'}
     567}}}
     568    * the above draws a line chart graph of field_2 as a function of field_1.
     569    * optionally, if field_2 contains data from multiple sources, which are differentiated by field_3, then the :group_by option draws a separate line per data source within the same graph
     570    * the legend for the X and Y axises can be set, as the format of the ticks to use
     571  * Specific options for pie chart
     572{{{
     573  g.mapping :value => :field_1, :label => :field_2
     574}}}
     575    * the above draws a pie chart where the values for a pie piece are taken from field_1 and the label to assign to that pie piece taken from field_2.
     576  * Specific options for histogram chart
     577{{{
     578  g.mapping :value => :field_1, :group_by => :field_2
     579  g.yaxis :legend => 'some_legend_for_y_axis'
     580  g.xaxis :legend => 'some_legend_for_x_axis', :ticks => {:format => ',.2f'}
     581}}}
     582    * the above chart draws an histogram of the values in field_1, using a automatic algorith to determine the histogram's bin size based on all the currently collected values
     583    * optionally, if field_1 contains data from multiple sources, which are differentiated by field_2, then the :group_by option draws a separate histogram bar per data source within the same graph
     584  * Examples:
     585{{{
     586# Assuming an application name 'ping' which defines the following measurement points:
     587#  app.defMeasurement('probe') do |m|
     588#    m.defMetric('dest_addr',:string)
     589#    m.defMetric('ttl',:uint32)
     590#    m.defMetric('rtt',:double)
     591#    m.defMetric('rtt_unit',:string)
     592#  end
     593#  app.defMeasurement('rtt_stats') do |m|
     594#    m.defMetric('min',:double)
     595#    m.defMetric('avg',:double)
     596#    m.defMetric('max',:double)
     597#    m.defMetric('mdev',:double)
     598#    m.defMetric('rtt_unit',:string)
     599#  end
     600
     601# Draw a line chart for RTT values against time, in addition draw a line per source
     602# that generated the probe data
     603#
     604defGraph 'RTT1' do |g|
     605  g.ms('probe').select {[ :oml_sender_id, :oml_ts_client, :oml_ts_server, :rtt ]}
     606  g.caption "Round Trip Time (RTT) reported by each resource"
     607  g.type 'line_chart3'
     608  g.mapping :x_axis => :oml_ts_client, :y_axis => :rtt, :group_by => :oml_sender_id
     609  g.xaxis :legend => 'time [s]'
     610  g.yaxis :legend => 'RTT [ms]', :ticks => {:format => 's'}
     611end
     612
     613# Draw a pie chart of the average RTT values other an entire ping run, using the sources
     614# that generated the measurement as labels for the pieces of the pie
     615#
     616defGraph 'RTT2' do |g|
     617  g.ms('rtt_stats').select {[ :oml_sender_id, :avg ]}
     618  g.caption "RTT Comparison Between Resources [ms]"
     619  g.type 'pie_chart2'
     620  g.mapping :value => :avg, :label => :oml_sender_id
     621end
     622
     623# Draw a histogram chart for RTT values, if many source generated these values, then
     624# draw adjacent bars for each source for a given bin
     625#
     626defGraph 'RTT3' do |g|
     627  g.ms('probe').select {[ :oml_sender_id, :oml_ts_client, :oml_ts_server, :rtt ]}.where("rtt < 1")
     628  g.caption "Histogram of RTT counts [ms]"
     629  g.type 'histogram2'
     630  g.mapping :value => :rtt, :group_by => :oml_sender_id
     631  g.yaxis :legend => 'Count'
     632  g.xaxis :legend => ' ', :ticks => {:format => ',.2f'}
     633end
     634}}}
     635
     636==== defPrototype ====
     637The defPrototype can be used to define a new prototype that can be reused as is, or customised through a set of parameters.
     638  * Usage
     639{{{
     640# Define a prototype with identifier "agressivePing"
     641
     642defPrototype("aggressivePing") do |proto|
     643  proto.description = "A node that flood packets to a destination to quickly determine packet drop rates"
     644  # Here we specify which property of the base application we would like to use
     645  # AND what are the default value we would like to give them.
     646
     647  # 'destination' is a mandatory parameter of ping, thus we do not set any default value
     648  proto.defProperty('destination', 'Name or IP address of the destination')
     649
     650  # This is a agressive ping, so we set the default probe number to 1000, we also set the
     651  # flooding behavior as 'true' by default, along with an interval of 0
     652  #
     653  proto.defProperty('numPacket', 'Number of probe packets to flood', 1000)
     654  proto.defProperty('enableFlood', 'Enable packet flooding', true)
     655  proto.defProperty('pktInterval', 'Time interval between packets in a flood', 0)
     656
     657  # Here we bind this prototype with the "pingApp" application definition. (Assuming "pingApp" has been defined)
     658  # And we also bind the properties that we decided to use and gave default values to
     659  #
     660  proto.addApplication("ping") do |app|
     661    app.bindProperty('dst', 'destination')
     662    app.bindProperty('count', 'numPacket')
     663    app.bindProperty('flood', 'enableFlood')
     664    app.bindProperty('interval', 'pktInterval')
     665  end
     666end
     667}}}
     668  * Use A Defined Prototype Within Group Context
     669After a prototype has been defined, it could be added to a group
     670{{{
     671# Define a group, The node(s) within this group will run the "aggressivePing" prototype/application
     672#
     673defGroup('worker', [node1, node2]) do |group|
     674   # Here we decide that the default 1000 probes are not enough in our case
     675   # and we give a specific value of 4000 to the 'numPacket' parameter of
     676   # the "aggressivePing" prototype
     677   #
     678   group.prototype("aggressivePing", {
     679     'destination' => "192.168.0.1",
     680     'numPacket' => 4000
     681   })
     682
     683   ...
     684end
     685}}}
     686=== Additional OMF EC Built-in Library ===
     687The OMF6 OEDL-enabled EC comes with some built-in OEDL libraries (i.e. really additional OEDL scripts) that can be optionally loaded by an experimenter within her own OEDL experiment scripts (using the above load_oedl command.
     688
     689==== omf_ec/backward/timeout_resources ====
     690  * This additional OEDL script implements a timeout timer for resources to checked-in an experiment
     691  * When it is loaded as part as another experiment, it will:
     692    * wait for the specified time in the experiment property 'oedl_timeout'
     693    * when that wait is over, it checks if all resources defined in the experiment have joined all their groups (also as defined in the experiment)
     694    * If not, then it stops the experiment.
     695  * The default timeout value is set here to 120 s. To modify that you should set in your experiment the oedl_timeout property to the desired timeout in second. This must be done prior to or as you load this script.
     696  * Usage example:
     697{{{
     698...
     699# This must be added within your own OEDL experiment
     700# (here we set the timeout to 180 s)
     701
     702loadOEDL('system:///omf_ec/backward/timeout_resources', { oedl_timeout: 180 })
     703...
     704}}}