Changes between Initial Version and Version 1 of General/1aClient


Ignore:
Timestamp:
Feb 18, 2019, 7:50:22 PM (5 years ago)
Author:
seskar
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • General/1aClient

    v1 v1  
     1= OML Client Programming =
     2
     3You can add support for OML reporting into your C and C++ applications by doing the following.
     4
     51. Include the oml2/omlc.h header file in your source file: #include <oml2/omlc.h>
     61. Call omlc_init(), passing in the command line arguments from main(), before doing any option processing in the rest of the code (the command line will be cleaned-up).
     71. Register measurement points
     81. Call omlc_start() to start the measurement collection process. At this point, interval-based measurement streams begin sampling.
     91. Call injector functions whenever you want to record a measurement sample.
     101. Call omlc_close() when your program has finished all measurement collection activities.
     111. Compile and link your program against the liboml2 library.
     12
     13The measurement points registration, as well as the injector functions are generated automatically by oml2-scaffold. We still describe the use of omlc_add_mp() and omlc_inject() towards the end of this documentation, as they are still used under the hood, but it is not recommended to use them directly.
     14
     15The oml2-scaffold automates the writing of repetitive code, provides better type-checking, is less error-prone and is more robust to API-changes. As an added bonus, it can also provide the skeleton for a new application (--main), its Makefile (--make) and option-parsing code (--opts).
     16
     17For a simple working example of the process and its results, see the sine generator in the source (source:example/liboml2).
     18
     19== Initializing OML: omlc_init()¶ ==
     20The first step in using OML is to initialize the client library by calling omlc_init(). This function takes four arguments:
     21
     22{{{
     23int omlc_init (const char* name, int* argc_ptr, const char** argv, o_log_fn logger)
     24}}}
     25
     26The 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, reporting messages to stderr).
     27
     28The 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():
     29
     30{{{
     31#include <oml2/omlc.h>
     32
     33int main(int argc, const char** argv)
     34{
     35  int result = omlc_init ("MyOmlClient", &argc, argv, NULL);
     36  if (result == -1) {
     37    fprintf (stderr, "Could not initialize OML\n");
     38    exit (1);
     39  }
     40
     41  /* Do application stuff... */
     42
     43  return 0;   
     44}
     45}}}
     46The 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).
     47
     48== Registering measurement points: oml2-scaffold(1)'s oml_register_mps() ==
     49
     50Measurement points obviously vary depending on the application. While OML has low-level functions to declare and register measurement points, it is easier to use a higher-level language to declare them in a meaningful human-readable way. For historic reasons (compatibility with OMF), we chose to use a DSL based on Ruby, but it is simple enough that no prior knowledge of the language is required. This file is manipulated with oml2-scaffold.
     51
     52This description file consists of three parts:
     53
     541. Application metadata (name, version, description; defApplication);
     551. (Optional) application command-line options (defProperty);
     561. Measurement points definition (defMeasurement).
     57
     58A template file can be generated with
     59
     60{{{
     61$ oml2-scaffold -a APPNAME # creates APPNAME.rb
     62}}}
     63
     64Here, we only focus on the defMeasurement commands. The full DSL is documented in oml2-scaffold.
     65
     66{{{
     67  a.defMeasurement(MPNAME) do |m|
     68    m.defMetric(FIELDNAME, 'FIELDTYPE')
     69  end
     70}}}
     71
     72This snippet defines a Measurement Point (MP) called MPNAME, with one field named FIELDNAME of type FIELDTYPE). You can have as many MP as you want, and each MP can contain a maximum of 64 FIELDS. Field types are documented in the OML generic interface, but the most common are
     73
     74* (u)int32 and (u)int64 for integers;
     75* double for double-precision numbers;
     76* string for nul-terminated character strings;
     77* blob for arbitrary binary data.
     78
     79Once the APPNAME.rb has been edited to accurately list the desired measurement points, the C code can be generated. It takes the form of a header.
     80
     81{{{
     82oml2-scaffold --oml APPNAME.rb
     83Created 'APPNAME_oml.h'
     84}}}
     85
     86This header declares several functions, including the MP-registration code.
     87You can then include the new header in the application code, and register all the MPs with the oml_register_mps() function. Internally, this function uses omlc_add_mp(), but you're much better off using this wrapper. For this function to be defined, it is required to #define OML_FROM_MAIN in one and only one source file of your application prior to #inclue'ing <oml2/omlc.h>. This instructs the headers to actually define the oml_register_mps() helper, as well as some necessary storage variables, rather than just declaring them as extern.
     88
     89{{{
     90#include <oml2/omlc.h>
     91#define OML_FROM_MAIN /* IMPORTANT: instruct APPNAME_oml.h to enable the generated code, rather than just declaring prototypes; do this in only one source file */
     92#include <APPNAME_oml.h>
     93
     94int main(int argc, const char** argv)
     95{
     96  int result = omlc_init ("MyOmlClient", &argc, argv, NULL);
     97  if (result == -1) {
     98    fprintf (stderr, "Could not initialize OML\n");
     99    exit (1);
     100  }
     101
     102  oml_register_mps();
     103
     104  /* Do application stuff.  From this point on we can safely call omlc_inject() */
     105  /* NOTE: if you used oml2-scaffold to generate the main program, it will contain a function "run" which does an omlc_inject_YourData() */
     106
     107  return 0;   
     108}
     109}}}
     110
     111Not declaring OML_FROM_MAIN would result in errors such as
     112
     113{{{
     114SRC.c:LINE:COL: error: implicit declaration of function ‘oml_register_mps’ [-Werror=implicit-function-declaration]
     115}}}
     116
     117or
     118
     119{{{
     120SRC.c:(.text+0x43a): undefined reference to `oml_register_mps'
     121SRC.c:(.text+0x446): undefined reference to `g_oml_mps_generator'
     122}}}
     123
     124while declaring it in more than one file would lead to the linker complaining that
     125
     126{{{
     127SRC2.o:(.data+0x0): multiple definition of `g_opts'
     128SRC.o:(.data+0x0): first defined here
     129SRC2.o:(.data+0x20): multiple definition of `options'
     130SRC.o:(.data+0x20): first defined here
     131SRC2.o:(.data+0x1c0): multiple definition of `g_oml_mps_generator'
     132SRC.o:(.data+0x1c0): first defined here
     133}}}
     134
     135Note: It is a good idea to put the creation of the APPNAME_oml.h file as part of the software-building process (e.g. in a Makefile) rather than keep the file static. This will provide more adpatability to future versions of OML. The APPNAME.rb file, however, can be safely checked in.
     136
     137== Starting OML measurement collection: omlc_start() ==
     138
     139Once 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:
     140
     141{{{
     142#include <oml2/omlc.h>
     143#define OML_FROM_MAIN
     144#include <APPNAME_oml.h>
     145
     146int main(int argc, const char** argv)
     147{
     148  int result = omlc_init ("MyOmlClient", &argc, argv, NULL);
     149  if (result == -1) {
     150    fprintf (stderr, "Could not initialize OML\n");
     151    exit (1);
     152  }
     153
     154  oml_register_mps();
     155
     156  result = omlc_start();
     157
     158  if (result == -1) {
     159    fprintf (stderr, "Error starting up OML measurement streams\n");
     160    exit (1);
     161  }
     162
     163  /* Do application stuff.  From this point on we can safely call omlc_inject() */
     164
     165  return 0;   
     166}
     167}}}
     168
     169If 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.
     170
     171== Recording measurements: oml2-scaffold(1)'s oml_inject_MPNAME() ==
     172
     173As part of the generated code, oml2-scaffold, has also written injection helpers for each MP you defined in your APPNAME.rb. They are named oml_inject_MPNAME, and have as many arguments as the number of fields in the MP, plus one. An exception is for blobs, where two arguments (a pointer to the blob data, and its size) are added.
     174
     175You can simply call these functions whenever there is data you want to report in a specific MP. The generic prototype of the injection helper is
     176
     177{{{
     178void oml_inject_MPNAME(OmlMP* mp, FIELDCTYPE FIELDNAME, ... )
     179}}}
     180
     181where mp is a variable, declared in APPNAME_oml.h and initialised by oml_register_mps() which relates to a specific measurement point. Their naming is regular: g_oml_mps_APPNAME->MPNAME. Then each field follow, in order, with the C-type matching their OML type. To inject in the previously discussed example MPNAME, the call would therefore be
     182
     183{{{
     184oml_inject_MPNAME(g_oml_mps_APPNAME->MPNAME, (int32_t) v);
     185}}}
     186
     187which assumes that the FIELDNAME mentionned above had a FIELDTYPE which is expressed as an int32_t in C.
     188Please note again, blobs injection requires two fields: void* BLOBMPNAME, ssize_t BLOBMPNAME_len.
     189
     190== Closing OML: omlc_close() ==
     191Once 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.
     192
     193The 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.
     194
     195== Compiling your OML application¶ ==
     196Once 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:
     197
     198{{{
     199$ gcc -c my-oml-client.c
     200$ gcc -o my-oml-client my-oml-client.o -loml2
     201}}}
     202
     203== Adding measurement points: omlc_add_mp() (DEPRECATED) ==
     204This still mostly works, but is no longer guaranteed, as it is much more brittle and error-prone than using oml2-scaffold
     205
     206Once 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.
     207
     208An 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).
     209
     210The description of an MP looks like this in C code:
     211
     212{{{
     213OmlMPDef mp_def [] =
     214{
     215  { "source", OML_LONG_VALUE },
     216  { "destination", OML_LONG_VALUE },
     217  { "length", OML_LONG_VALUE },
     218  { "protocol", OML_STRING_VALUE },
     219  { NULL, (OmlValueT)0 }
     220};
     221}}}
     222
     223As 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.
     224
     225The last element of the array is a sentinel that marks the end of the MP definition. It must always be included as shown.
     226
     227To register this MP with OML, you must call the omlc_add_mp() function, like this:
     228
     229{{{
     230  OmlMP* mp = omlc_add_mp ("packet_info", mp_def);
     231
     232  if (mp == NULL) {
     233    fprintf (stderr, "Error: could not register Measurement Point \"packet_info\"");
     234    exit (1);
     235  }
     236}}}
     237 
     238Here 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.
     239
     240If 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.
     241
     242== Recording measurements: omlc_inject() (DEPRECATED) ==
     243This still mostly works, but is no longer guaranteed, as it is much more brittle and error-prone than using oml2-scaffold
     244
     245If omlc_start() is successful, you can now call omlc_inject() to record your application's measurements. The protoype for omlc_inject() is:
     246
     247{{{
     248void omlc_inject (OmlMP *mp, OmlValueU *values);
     249}}}
     250
     251The 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.
     252
     253OmlValueU is implemented as a union, but it should not be manipulated directly. Instead, OML provides the following macros to set up the OmlValueU array:
     254
     255{{{
     256omlc_set_long(var, value);
     257omlc_set_double(var, value);
     258omlc_set_string(var, value);
     259omlc_set_const_string(var, value);
     260}}}
     261
     262Here 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.
     263
     264{{{
     265long source_id;
     266long destination_id;
     267long packet_length;
     268char *protocol;
     269
     270...
     271
     272/* Some application-specific code to obtain values for the variables above */
     273
     274...
     275
     276OmlValueU values[4];
     277
     278omlc_zero_array(values, 4); /* Make sure all internal pointers are initially NULL to avoid segmentation faults and weird errors, see OmlValueU(3) */
     279
     280omlc_set_long (values[0], source_id);
     281omlc_set_long (values[1], destination_id);
     282omlc_set_long (values[2], packet_length);
     283omlc_set_string (values[3], protocol);
     284
     285omlc_inject (mp, values);
     286
     287omlc_reset_string(values[3]); /* Free allocated memory (see also omlc_reset_blob(3)) */
     288}}}
     289
     290The 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.
     291
     292If 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.