Changes between Initial Version and Version 1 of Developer/4aFilters


Ignore:
Timestamp:
Jul 15, 2020, 3:50:43 PM (4 years ago)
Author:
seskar
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Developer/4aFilters

    v1 v1  
     1= Developing Filters =
     2
     3''This document is a work in progress''
     4
     5An OML filter is a component providing some filtering functions complying with an OML API. It accepts one or more samples of one or more values (i.e. fields from MPs) via an (*oml_filter_input) function (usually named sample, and called once per sample), and reports one or more filtered values when queried via an (*oml_filter_output) function (usually named process). It also possesses an internal state (*InstanceData for clarity, which is typedef'd to the actual structure defined in the relevant header). A couple of other helper functions are also defined to allow registering the filter to OML, determining its input and output schemas, and creating instances of the filter.
     6
     7The API is defined in source:lib/client/oml2/oml_filter.h.
     8
     9== Structure of a Filter ==
     10
     11In this section, examples are directly based on source:lib/client/filter/first_filter.c and source:lib/client/filter/first_filter.h. The first filter reports the first data sample from a period.
     12
     13=== Header ===
     14The header file for a filter is where the structure defining its internal state is defined.
     15
     16The first filter only needs to store the value of the first sample it has seen. However, it also needs to keep track of whether it has seen a sample yet. Therefore, its internal state consists of two elements:
     17 * a OmlValue (or more precisely a pointer where) to store the element;
     18 * an int which is set to 1 if the first element hasn't been seen during the sampling period yet.
     19
     20Refer to source:lib/client/filter/first_filter.h for a full header.
     21
     22=== Implementation ===
     23
     24The life of a filter is as follows tells OML about its methods and its output schema.
     25
     26Filter instances can then be created.
     27 1. Creation of a new instance and its related internal data ((*oml_filter_create)),
     28 1. Parameters setting ((*oml_filter_set)),
     29 1. Injection of samples ((*oml_filter_input)),
     30 1. Query for the filtered output ((*oml_filter_output)),
     31 1. Function to start a new sampling period ((*oml_filter_output)).
     32
     33The registration function is a simple code which calls omlf_register_filter() to tell OML about the methods it provide implementing the OML filter API. It also specifies the name by which the filter will be known from OML clients (e.g. the configuration file). The omlf_register_filter() takes its parameters in the following order:
     34
     35 1. const char* filter_name: name of the filter,
     36 1. void* (*oml_filter_create) (OmlValueT type, OmlValue* result): pointer to the instance creation function,
     37 1. int (*oml_filter_set) (struct _omlFilter* filter, const char* name, OmlValue* value): pointer to the filter parameter-setting function,
     38 1. int (*oml_filter_input) (struct _omlFilter* filter, OmlValue* value): pointer to the sample submission function,
     39 1. int (*oml_filter_output) (struct _omlFilter* filter, struct _omlWriter* writer): pointer to the function writing the filtered data (using the given writer),
     40 1. int (*oml_filter_newwindow) (struct _omlFilter* filter): pointer to the function clearing the filter's state to start a new sampling window,
     41 1. int (*oml_filter_meta)(struct _omlFilter* filter, int index_offset, char** namePtr, OmlValueT* type): see below,
     42 1. OmlFilterDef* filter_def: see below.
     43
     44The two last parameters are used specification of the filter's output schema via either an OmlFilterDef array (a null-terminated list of named fields of a given type), or by using
     45
     46{{{
     47int (*oml_filter_meta)(struct _omlFilter filter, int index_offset, char** namePtr,  OmlValueT* type)
     48}}}
     49
     50(which provides a name and a type for a given index). FIXME: The (*oml_filter_meta) parameter takes precedence if both are provided, but may still inspect the OmlFilterDef. In an OmlFilterDef, the specific type OML_INPUT_VALUE can be used to indicate that the output schema for this field is the same as that of the input field. An example registration function could simply look like (and most of them actually are) as follows.
     51{{{
     52void
     53omlf_register_filter_first (void)
     54{
     55  OmlFilterDef def [] =
     56    {
     57      { "first", OML_INPUT_VALUE },
     58      { NULL, 0 }
     59    };
     60
     61  omlf_register_filter ("first",
     62            omlf_first_new,
     63            NULL,
     64            sample,
     65            process,
     66            newwindow,
     67            meta,
     68            def);
     69}
     70}}}
     71The role of the
     72{{{
     73void* (*oml_filter_create) (OmlValueT type,  OmlValue* result)
     74}}}
     75
     76function is to initialise a new InstanceData for the filter, to which it returns a pointer. This is then included by OML into a proper filter instance type, struct _omlFilter*, which is passed as f to the other method. The InstanceData is accessible from there as (InstanceData*)f->instance_data, and usually used as self. The type argument contains the data type of its input. FIXME: What if several inputs? The result argument is a pointer OML-initialised structure corresponding to the filter's output schema (as derived through the OmlFilterDef structure and the (*oml_filter_meta) function). The actual instance of the filter is really its InstanceData, corresponding to the structure defined in the header. The role of the instance-creation function is thus to allocate memory for a new such structure, and initialise it depending on the parameters, then return it to OML.
     77
     78XXX: design issue: why store OmlValue* result in the InstanceData rather than in the filter itself and access it as f->result? The skeleton of such a function typically is
     79{{{
     80void*
     81omlf_EXAMPLE_new(
     82  OmlValueT type,
     83  OmlValue* result
     84) {
     85  int i;
     86  InstanceData* self = (InstanceData *)malloc(sizeof(InstanceData));
     87
     88  if (self) {
     89    memset(self, 0, sizeof(InstanceData));
     90
     91    self->result = result;
     92    self->result[0].type = type; /* FIXME: What if there are several outputs of various types? */
     93    /* Initialise other instance data here*/
     94
     95  } else {
     96    logerror ("Could not allocate %d bytes for EXAMPLE filter instance data\n",
     97        sizeof(InstanceData));
     98    return NULL;
     99  }
     100
     101  return self;
     102}
     103}}}
     104FIXME: How can the
     105{{{
     106int (*oml_filter_set) (struct _omlFilter* filter, const char* name, OmlValue* value)
     107}}}
     108function be used?
     109
     110The measurement samples are reported to the filter via its
     111
     112{{{
     113int (*oml_filter_input) (struct _omlFilter* filter, OmlValue* value)
     114}}}
     115
     116The value can be copied verbatim (which the filter does), or accessed using such functions as oml_value_to_double() before being stored in, and depending on the content of, the InstanceData. A basic input function (which just copies the sample over its result - therefor creating some sort of a filter) would look as follow:
     117{{{
     118#include "oml_value.h"
     119
     120/* ... */
     121
     122static int
     123sample(
     124    OmlFilter* f,
     125    OmlValue * value  //! values of sample
     126) {
     127  InstanceData* self = (InstanceData*)f->instance_data;
     128  OmlValueU* v = &value->value;
     129  OmlValueT type = value->type;
     130
     131  if (type != self->result[0].type) {
     132    logerror ("Different type from initial definition\n");
     133    return 0;
     134  }
     135  return oml_value_copy(v, type, &self->result[0]);
     136
     137  return 0;
     138}
     139}}}
     140
     141When a filter output is required (after the required number of samples has been received, or the sampling time has elapsed), OML calls the
     142
     143{{{
     144int (*oml_filter_output) (struct _omlFilter* filter, struct _omlWriter* writer)
     145}}}
     146
     147function to have the filter output the filterred data using the provided writer. This is typically done by filling self->result (i.e. the OmlValue* obtained at the creation of the instance which has been stored in the InstanceData), then passing it to the writer as
     148writer->out(writer, self->result, f->output_count);
     149
     150A more thorough skeleton for such a function would be
     151{{{
     152static int
     153process(
     154  OmlFilter* f,
     155  OmlWriter*  writer //! Write results of filter to this function
     156) {
     157  InstanceData* self = (InstanceData*)f->instance_data;
     158
     159  writer->out(writer, self->result, f->output_count);
     160
     161  /* Prepare the instance for a new sampling period */
     162
     163  return 0;
     164}
     165}}}
     166
     167Note that all but the filter registration function are static, as they are only referenced directly by the registration function, and later only called using function pointers by OML.
     168
     169Refer to source:lib/client/filter/first_filter.c for a full implementation.
     170
     171=== Adding a unit test for the filter ===
     172The filters' unit test is in test/lib/check_liboml2_filters.c add a test for your new filter there. Test at least creation and output (test_filter_FILTER_{create,output}). Follow the examples already in the file (the averaging filter is a good starting point).
     173
     174This is not optional.
     175
     176== Registering the filter ==
     177=== From OML ===
     178A call to the filter's function which encapsulates omlf_register_filter() is sufficient. It should be added to function register_builtin_filters in source:lib/client/filter/factory.c. As the fi lter's own registration function is not declared in any header included by this file, its prototype (typically void rf(void)) has to be repeated just before the definition of register_builtin_filters.
     179
     180For the new filter's code to be included in the build, references to its header and implementation files must be added to source:lib/client/Makefile.am, in variable liboml2_la_SOURCES.
     181
     182=== From an OML client ===
     183
     184== Compiling the filter outside of OML ==