OML Client Programming

You can include OML measurement collection into your C and C++ applications by doing the following:

  1. Include the oml2/omlc.h header file in your source file: #include <oml2/omlc.h>
  2. Call omlc_init(), passing in the command line arguments from main(), before doing any option processing in your own code.
  3. Add measurement points using omlc_add_mp()
  4. Call omlc_start() to start the measurement collection process. At this point, interval-based measurement streams begin sampling.
  5. Call omlc_inject() whenever you want to record a measurement sample.
  6. Call omlc_close() when your program has finished all measurement collection activities.
  7. Compile and link your program against the liboml2 library.

Initializing OML: omlc_init()

The first step in using OML is to initialize the client library by calling omlc_init(). This function takes four arguments:

int omlc_init (const char* name, int* argc_ptr, const char** argv, o_log_fn logger)

The last argument is for custom logging and can safely be set to NULL for most applications (OML uses its own internal logging functions in that case).

The first argument is the name of the application, for example "MyOmlClient". The second and third arguments should come from the argc and argv arguments to the C main() function. OML recognizes a set of command line arguments for configuring its internal operation, and the omlc_init() function process the command line, reading options that are relevant to OML, which all start with an "--oml" prefix. omlc_init() also removes its own arguments from the command line argument vector, and sets argc equal to the number of remaining arguments. This means that you can then go on to process command line arguments that are relevant to your own application, without having OML's arguments polluting your argv array. Here is a typical call to omlc_init():

#include <oml2/omlc.h>

int main(int argc, const char** argv)
{
  int result = omlc_init ("MyOmlClient", &argc, argv, NULL);
  if (result == -1) {
    fprintf (stderr, "Could not initialize OML\n");
    exit (1);
  }

  /* Do application stuff... */

  return 0;    
}

The omlc_init() function returns 0 on success and -1 on failure. Reasons for a failure will appear in the OML log file (this is true of all functions in the OML API).

Adding measurement points: omlc_add_mp()

Once your application has initialized the OML client library, you should create some measurement points. A measurement point (MP) is an application-defined input port for recording measurements. An application "injects" measurements into the MP, and the client library registers them, applies filters to them, then sends them to an output stream, either a file or a network socket connected to an oml2-server.

An MP accepts n-tuples as inputs. That is, each measurement is not just one number, but can be a group of related measurement items. For instance, a measurement might represent all the available information about a received packet, such as its source and destination, its length, and what protocol it carries. Integers, floating point numbers, and short strings can be represented (and we have plans to add new data types in the near future).

The description of an MP looks like this in C code:

OmlMPDef mp_def [] =
{
  { "source", OML_LONG_VALUE },
  { "destination", OML_LONG_VALUE },
  { "length", OML_LONG_VALUE },
  { "protocol", OML_STRING_VALUE },
  { NULL, (OmlValueT)0 }
};

As you can see, the MP definition is represented as an array of OmlMPDef structures. The first member of each struct is the name, as a string. The second is the type of that element of the n-tuple. Not shown in this example is the OML_DOUBLE_VALUE type, for floating point values.

The last element of the array is a sentinel that marks the end of the MP definition. It must always be included as shown.

To register this MP with OML, you must call the omlc_add_mp() function, like this:

  OmlMP* mp = omlc_add_mp ("packet_info", mp_def);

  if (mp == NULL) {
    fprintf (stderr, "Error: could not register Measurement Point \"packet_info\"");
    exit (1);
  }  

Here we have given the MP the name "packet_info". This is used in the schema for the measurement output, and in database table column names when the measurements are sent to an OML server. Again, if omlc_add_mp() fails for some reason, it signals an error by returning NULL, and your application should check for this case.

If omlc_add_mp() succeeds, then it returns a pointer to an OmlMP object, which you should treat as an opaque handle representing your particular MP, which is used in subsequent calls to omlc_inject() (see below). You can define multiple MP's in an application by calling omlc_add_mp() multiple times. Each time it is called, it will return a pointer to a different, unique OmlMP object.

Starting OML measurement collection: omlc_start()

Once you have defined all of your MP's, you should call omlc_start(). This sets up the filter configuration for each measurement point and for each data destination, which is specified by the user of your application at runtime, either using the command line options or using the XML configuration file (for details about this, see Configuring_OML_Client_Applications). For example:

#include <oml2/omlc.h>

int main(int argc, const char** argv)
{
  int result = omlc_init ("MyOmlClient", &argc, argv, NULL);
  if (result == -1) {
    fprintf (stderr, "Could not initialize OML\n");
    exit (1);
  }

  /* Multiple calls to omlc_add_mp() ... */

  result = omlc_start();

  if (result == -1) {
    fprintf (stderr, "Error starting up OML measurement streams\n");
    exit (1);
  }

  /* Do application stuff.  From this point on we can safely call omlc_inject() */

  return 0;    
}

If there was some problem with the configuration, omlc_start() will return -1, indicating a failure. If omlc_start() fails, no measurement can be performed, because the necessary filters and data destinations will not have been set up correctly. Any subsequent calls to omlc_inject() will be ignored by the OML client library in that case, however your application will continue to run. If omlc_start() returns 0, then no error occurred and all subsequent calls to omlc_inject() will successfully record measurements.

Recording measurements: omlc_inject()

If omlc_start() is successful, you can now call omlc_inject() to record your application's measurements. The protoype for omlc_inject() is:

void omlc_inject (OmlMP *mp, OmlValueU *values);

The function accepts an OmlMP* as its first argument -- this is the MP into which the measurement will be injected. It should be obtained from a call to omlc_add_mp(), see above. The second argument is an array of OmlValueU objects. An OmlValueU is an untyped representation of a value. Each element of the array should have the same type as the corresponding element of the OmlMPDef used to create the MP.

OmlValueU is implemented as a union, but it should not be manipulated directly. Instead, OML provides the following macros to set up the OmlValueU array:

omlc_set_long(var, value);
omlc_set_double(var, value);
omlc_set_string(var, value);
omlc_set_const_string(var, value);

Here is an example of how to inject a measurement value into an MP. The MP in the example uses the same definition as the example shown for the omlc_add_mp() function above.

long source_id;
long destination_id;
long packet_length;
char *protocol;

...

/* Some application-specific code to obtain values for the variables above */ 

...

OmlValueU values[4];

omlc_set_long (values[0], source_id);
omlc_set_long (values[1], destination_id);
omlc_set_long (values[2], packet_length);
omlc_set_string (values[3], protocol);

omlc_inject (mp, values);

The function omlc_inject() does not return a value. If OML is not configured properly then it fails silently without recording any measurements. This allows your application to continue working even if there is a problem with OML.

If you call omlc_inject() before calling omlc_start() or after calling omlc_close(), then the call will also be ignored and no measurements will be recorded.

Closing OML: omlc_close()

Once you have finished all measurement activities you can call omlc_close() to shut down the measurement system. You should do this before exiting your application. It ensures that all measurement streams are flushed and all measurements are recorded (or at least sent to the file or server). If omlc_close() is not called before exit then some measurements might be lost.

The OML client library installs a signal handler to handle a user quit signal (SIGINT, sent when you press Control-C on the command line) and to detect server disconnection and handle it gracefully. In the case of a SIGINT signal, the signal handler calls omlc_close() before exiting the application.

Compiling your OML application

Once you have written your OML application code, it must be compiled and linked against the OML client library, liboml2. On unix systems with gcc, you can do this as follows:

$ gcc -c my-oml-client.c
$ gcc -o my-oml-client my-oml-client.o -loml2