diff --git a/etc/example.conf b/etc/example.conf index 4da8649a5..acab53b2d 100644 --- a/etc/example.conf +++ b/etc/example.conf @@ -148,8 +148,6 @@ paths = ( rate = 100, # Send message over this path with a fixed (constant) rate (default: 0). # Setting this value to 0 will disable this feature. - hook = "print", # Register custom hook funktion (see src/hooks.c) - poolsize = 30 # The amount of samples which are kept in a circular buffer. # This number must be larger than the 'vectorize' settings of all # associated input and output nodes! @@ -160,23 +158,31 @@ paths = ( in = "opal_node", # There's only a single source node allowed! out = [ "udp_node", "tcp_node" ], # Multiple destination nodes are supported too. - - hook = [ "print", "decimate:10" ] # Same is true for hooks. - # Multipe hook functions are executed in the order they are specified here. }, { in = "socket_node", out = "file_node", # This path includes all available example hooks. - hook = [ - "ts", # Replace the timestamp of messages with the time of reception - "skip_unchanged:0.1", # Skip messages whose values have not changed more than 0.1 from the previous message. - "skip_first:10", # Skip all messages which have been received in the first 10 seconds - "print", # Print all messages to stdout - "decimate:2", # Only forward every 2nd message - "convert:fixed", # Convert all values to fixed precission. Use 'float' to convert to floating point. - "fir:0" # Apply finite impulse response filter to first value. - # Coefficients are hard-coded in 'include/config.h'. - ] + # A complete list of supported hooks + print = { + output = "stdout" + priority = 0 + }, + ts = { + priority = 1 + } + decimate = { + ratio = 2 # Only forward every 2nd message + }, + convert = { + mode = "fixed" # Convert all values to fixed precission. Use 'float' to convert to floating point. + }, + skip_first = { + seconds = 10 # Skip the first 10 seconds of this path + }, + shift = { + mode = "origin", # Shift origin timestam of samples by +10 seconds + offset = 10 + } } ); diff --git a/etc/gtnet-skt/test1.conf b/etc/gtnet-skt/test1.conf index 529d0e755..93419a814 100644 --- a/etc/gtnet-skt/test1.conf +++ b/etc/gtnet-skt/test1.conf @@ -40,6 +40,10 @@ paths = ( { in = "node1", # Name of the node we listen to (see above) out = "node1", # And we loop back to the origin - hook = ["print"] + + # Hooks + print = { + output = "stdout" + } } ); diff --git a/etc/gtnet-skt/test2.conf b/etc/gtnet-skt/test2.conf index 9ce5cdd51..0cfe1d216 100644 --- a/etc/gtnet-skt/test2.conf +++ b/etc/gtnet-skt/test2.conf @@ -40,6 +40,10 @@ paths = ( { in = "node1", # Name of the node we listen to (see above) out = "node2", # And we loop back to the origin - hook = ["print"] + + # Hooks + print = { + output = "stdout" + } } ); diff --git a/etc/gtnet-skt/test3.conf b/etc/gtnet-skt/test3.conf index b90f314f1..327f7e2e4 100644 --- a/etc/gtnet-skt/test3.conf +++ b/etc/gtnet-skt/test3.conf @@ -40,6 +40,10 @@ paths = ( { in = "node1", # Name of the node we listen to (see above) out = "node2", # And we loop back to the origin - hook = ["print"] + + # Hooks + print = { + output = "stdout" + } } ); diff --git a/etc/gtnet-skt/test4.conf b/etc/gtnet-skt/test4.conf index 08e4165a5..cfc8a669c 100644 --- a/etc/gtnet-skt/test4.conf +++ b/etc/gtnet-skt/test4.conf @@ -40,6 +40,10 @@ paths = ( { in = "node1", # Name of the node we listen to (see above) out = "node1", # And we loop back to the origin - hook = ["print"] + + # Hooks + print = { + output = "stdout" + } } ); diff --git a/etc/gtnet-skt/test5.conf b/etc/gtnet-skt/test5.conf index 508d5edfa..d93549669 100644 --- a/etc/gtnet-skt/test5.conf +++ b/etc/gtnet-skt/test5.conf @@ -41,6 +41,10 @@ paths = ( { in = "node1", # Name of the node we listen to (see above) out = "node1", # And we loop back to the origin - hook = ["print"] + + # Hooks + print = { + output = "stdout" + } } ); diff --git a/etc/loopback.conf b/etc/loopback.conf index e8db4b117..c40a0d320 100644 --- a/etc/loopback.conf +++ b/etc/loopback.conf @@ -62,6 +62,5 @@ paths = ( { in = "node1", # Name of the node we listen to (see above) out = "node2", # And we loop back to the origin - hook = ["decimate:2", "print"] } ); diff --git a/include/villas/hook.h b/include/villas/hook.h index 9ed1d2c0b..6856a9684 100644 --- a/include/villas/hook.h +++ b/include/villas/hook.h @@ -96,19 +96,21 @@ enum hook_when { struct hook_type { enum hook_when when; /**< The type of the hook as a bitfield */ hook_cb_t cb; /**< The hook callback function as a function pointer. */ - int priority; /**< A priority to change the order of execution within one type of hook */ + int priority; /**< Default priority of this hook type. */ }; /** Descriptor for user defined hooks. See hooks[]. */ struct hook { enum state state; - - const char *parameter; /**< A parameter string for this hook. Can be used to configure the hook behaviour. */ - + struct sample *prev, *last; struct hook_type *_vt; /**< C++ like Vtable pointer. */ void *_vd; /**< Private data for this hook. This pointer can be used to pass data between consecutive calls of the callback. */ + + int priority; /**< A priority to change the order of execution within one type of hook. */ + + config_setting_t *cfg; }; /** Save references to global nodes, paths and settings */ @@ -142,10 +144,18 @@ int hook_cmp_priority(const void *a, const void *b); */ void * hook_storage(struct hook *h, int when, size_t len, ctor_cb_t ctor, dtor_cb_t dtor); -/** Parse an array or single hook function. +/** Parses an object of hooks * - * Examples: - * hooks = [ "print", "fir" ] - * hooks = "log" + * Example: + * + * { + * stats = { + * output = "stdout" + * }, + * skip_first = { + * seconds = 10 + * }, + * hooks = [ "print" ] + * } */ -int hook_parse_list(struct list *list, config_setting_t *cfg); \ No newline at end of file +int hook_parse_list(struct list *list, config_setting_t *cfg, struct super_node *sn); \ No newline at end of file diff --git a/lib/hook.c b/lib/hook.c index 9b56533e2..c4f0954de 100644 --- a/lib/hook.c +++ b/lib/hook.c @@ -31,6 +31,7 @@ int hook_init(struct hook *h, struct hook_type *vt, struct super_node *sn) assert(h->state == STATE_DESTROYED); h->_vt = vt; + h->priority = vt->priority; ret = hook_run(h, HOOK_INIT, &i); if (ret) @@ -43,29 +44,14 @@ int hook_init(struct hook *h, struct hook_type *vt, struct super_node *sn) int hook_parse(struct hook *h, config_setting_t *cfg) { - const char *hookline; - char *name, *param; - struct plugin *p; int ret; - + assert(h->state != STATE_DESTROYED); - hookline = config_setting_get_string(cfg); - if (!hookline) - cerror(cfg, "Invalid hook function"); + h->cfg = cfg; - name = strtok((char *) hookline, ":"); - param = strtok(NULL, ""); + config_setting_lookup_int(h->cfg, "priority", &h->priority); - p = plugin_lookup(PLUGIN_TYPE_HOOK, name); - if (!p) - cerror(cfg, "Unknown hook function '%s'", name); - - if (p->hook.when & HOOK_AUTO) - cerror(cfg, "Hook '%s' is built-in and can not be added manually.", name); - - h->parameter = param; - /* Parse hook arguments */ ret = hook_run(h, HOOK_PARSE, NULL); if (ret) @@ -96,12 +82,12 @@ int hook_cmp_priority(const void *a, const void *b) struct hook *ha = (struct hook *) a; struct hook *hb = (struct hook *) b; - return ha->_vt->priority - hb->_vt->priority; + return ha->priority - hb->priority; } int hook_run(struct hook *h, int when, struct hook_info *i) { - debug(LOG_HOOK | 22, "Running hook '%s' when=%u, prio=%u, cnt=%zu", plugin_name(h->_vt), when, h->_vt->priority, i ? i->count : 0); + debug(LOG_HOOK | 22, "Running hook '%s' when=%u, prio=%u, cnt=%zu", plugin_name(h->_vt), when, h->priority, i ? i->count : 0); return h->_vt->when & when ? h->_vt->cb(h, when, i) : 0; } @@ -129,33 +115,40 @@ void * hook_storage(struct hook *h, int when, size_t len, ctor_cb_t ctor, dtor_c return h->_vd; } -/** Parse an array or single hook function. - * - * Examples: - * hooks = [ "print", "fir" ] - * hooks = "log" - */ -int hook_parse_list(struct list *list, config_setting_t *cfg) +int hook_parse_list(struct list *list, config_setting_t *cfg, struct super_node *sn) { struct hook h; + struct plugin *p; + + int ret; + + if (!config_setting_is_group(cfg)) + cerror(cfg, "Hooks must be configured with an object"); - switch (config_setting_type(cfg)) { - case CONFIG_TYPE_STRING: - hook_parse(&h, cfg); - list_push(list, memdup(&h, sizeof(h))); - break; + int priority = 10; + for (int i = 0; i < config_setting_length(cfg); i++) { + config_setting_t *cfg_hook = config_setting_get_elem(cfg, i); + + const char *name = config_setting_name(cfg_hook); + + p = plugin_lookup(PLUGIN_TYPE_HOOK, name); + if (!p) + continue; /* We ignore all non hook settings in this libconfig object setting */ + + if (!config_setting_is_group(cfg_hook)) + cerror(cfg_hook, "The 'hooks' setting must be an array of strings."); + + ret = hook_init(&h, &p->hook, sn); + if (ret) + continue; + + h.priority = priority++; + + ret = hook_parse(&h, cfg_hook); + if (ret) + continue; - case CONFIG_TYPE_ARRAY: - for (int i = 0; i < config_setting_length(cfg); i++) { - config_setting_t *cfg_hook = config_setting_get_elem(cfg, i); - - hook_parse(&h, cfg_hook); - list_push(list, memdup(&h, sizeof(h))); - } - break; - - default: - cerror(cfg, "Invalid hook functions"); + list_push(list, memdup(&h, sizeof(h))); } return list_length(list); diff --git a/lib/hooks/convert.c b/lib/hooks/convert.c index 69677e611..22fc3dd3a 100644 --- a/lib/hooks/convert.c +++ b/lib/hooks/convert.c @@ -20,17 +20,22 @@ static int hook_convert(struct hook *h, int when, struct hook_info *j) } mode; } *private = hook_storage(h, when, sizeof(*private), NULL, NULL); + const char *mode; + switch (when) { case HOOK_PARSE: - if (!h->parameter) - error("Missing parameter for hook: '%s'", plugin_name(h->_vt)); - - if (!strcmp(h->parameter, "fixed")) + if (!h->cfg) + error("Missing configuration for hook: '%s'", plugin_name(h->_vt)); + + if (!config_setting_lookup_string(h->cfg, "mode", &mode)) + cerror(h->cfg, "Missing setting 'mode' for hook '%s'", plugin_name(h->_vt)); + + if (!strcmp(mode, "fixed")) private->mode = TO_FIXED; - else if (!strcmp(h->parameter, "float")) + else if (!strcmp(mode, "float")) private->mode = TO_FLOAT; else - error("Invalid parameter '%s' for hook 'convert'", h->parameter); + error("Invalid parameter '%s' for hook 'convert'", mode); break; case HOOK_READ: @@ -56,7 +61,7 @@ static struct plugin p = { .hook = { .priority = 99, .cb = hook_convert, - .when = HOOK_STORAGE | HOOK_READ + .when = HOOK_STORAGE | HOOK_PARSE | HOOK_READ } }; diff --git a/lib/hooks/decimate.c b/lib/hooks/decimate.c index 4a6338258..30737db03 100644 --- a/lib/hooks/decimate.c +++ b/lib/hooks/decimate.c @@ -14,22 +14,24 @@ static int hook_decimate(struct hook *h, int when, struct hook_info *j) { struct { - unsigned ratio; + int ratio; unsigned counter; } *private = hook_storage(h, when, sizeof(*private), NULL, NULL); switch (when) { - case HOOK_PARSE: - if (!h->parameter) - error("Missing parameter for hook: '%s'", plugin_name(h->_vt)); - - private->ratio = strtol(h->parameter, NULL, 10); - if (!private->ratio) - error("Invalid parameter '%s' for hook 'decimate'", h->parameter); - + case HOOK_INIT: private->counter = 0; break; + case HOOK_PARSE: + if (!h->cfg) + error("Missing configuration for hook: '%s'", plugin_name(h->_vt)); + + if (!config_setting_lookup_int(h->cfg, "ratio", &private->ratio)) + cerror(h->cfg, "Missing setting 'ratio' for hook '%s'", plugin_name(h->_vt)); + + break; + case HOOK_READ: assert(j->samples); @@ -57,7 +59,7 @@ static struct plugin p = { .hook = { .priority = 99, .cb = hook_decimate, - .when = HOOK_STORAGE | HOOK_DESTROY | HOOK_READ + .when = HOOK_STORAGE | HOOK_PARSE | HOOK_DESTROY | HOOK_READ } }; diff --git a/lib/hooks/shift.c b/lib/hooks/shift.c index 60e4496be..e016d978f 100644 --- a/lib/hooks/shift.c +++ b/lib/hooks/shift.c @@ -27,55 +27,55 @@ static int hook_shift(struct hook *h, int when, struct hook_info *j) SHIFT_SEQUENCE } mode; } *private = hook_storage(h, when, sizeof(*private), NULL, NULL); + + const char *mode; switch (when) { + case HOOK_INIT: + private->mode = SHIFT_TS_ORIGIN; /* Default mode */ + break; + case HOOK_PARSE: - if (!h->parameter) - error("Missing parameter for hook: '%s'", plugin_name(h->_vt)); + if (!h->cfg) + error("Missing configuration for hook: '%s'", plugin_name(h->_vt)); - char *endptr, *off; - - char *cpy = strdup(h->parameter); - char *tok1 = strtok(cpy, ","); - char *tok2 = strtok(NULL, ","); - - if (tok2) { - off = tok2; - - if (!strcmp(tok1, "origin")) + if (config_setting_lookup_string(h->cfg, "mode", &mode)) { + if (!strcmp(mode, "origin")) private->mode = SHIFT_TS_ORIGIN; - else if (!strcmp(tok1, "received")) + else if (!strcmp(mode, "received")) private->mode = SHIFT_TS_RECEIVED; - else if (!strcmp(tok1, "sent")) + else if (!strcmp(mode, "sent")) private->mode = SHIFT_TS_SENT; - else if (!strcmp(tok1, "sequence")) + else if (!strcmp(mode, "sequence")) private->mode = SHIFT_SEQUENCE; else - error("Invalid mode parameter for hook '%s'", plugin_name(h->_vt)); - } - else { - off = tok1; - - private->mode = SHIFT_TS_ORIGIN; /* Default mode */ + error("Invalid mode parameter '%s' for hook '%s'", mode, plugin_name(h->_vt)); } switch (private->mode) { case SHIFT_TS_ORIGIN: case SHIFT_TS_RECEIVED: - case SHIFT_TS_SENT: - private->offset.ts = time_from_double(strtod(off, &endptr)); + case SHIFT_TS_SENT: { + double offset; + + if (!config_setting_lookup_float(h->cfg, "offset", &offset)) + cerror(h->cfg, "Missing setting 'offset' for hook '%s'", plugin_name(h->_vt)); + + private->offset.ts = time_from_double(offset); break; + } - case SHIFT_SEQUENCE: - private->offset.seq = strtoul(off, &endptr, 10); + case SHIFT_SEQUENCE: { + int offset; + + if (!config_setting_lookup_int(h->cfg, "offset", &offset)) + cerror(h->cfg, "Missing setting 'offset' for hook '%s'", plugin_name(h->_vt)); + + private->offset.seq = offset; break; + } } - if (endptr == off) - error("Invalid offset parameter for hook '%s'", plugin_name(h->_vt)); - - free(cpy); - break; case HOOK_READ: diff --git a/lib/hooks/skip_first.c b/lib/hooks/skip_first.c index 00c867a99..902bb3123 100644 --- a/lib/hooks/skip_first.c +++ b/lib/hooks/skip_first.c @@ -8,6 +8,8 @@ * @{ */ +#include + #include "hook.h" #include "plugin.h" #include "timing.h" @@ -24,20 +26,19 @@ static int hook_skip_first(struct hook *h, int when, struct hook_info *j) } state; } *private = hook_storage(h, when, sizeof(*private), NULL, NULL); - char *endptr; - double wait; - switch (when) { - case HOOK_PARSE: - if (!h->parameter) - error("Missing parameter for hook: '%s'", plugin_name(h->_vt)); - - wait = strtof(h->parameter, &endptr); - if (h->parameter == endptr) - error("Invalid parameter '%s' for hook 'skip_first'", h->parameter); + case HOOK_PARSE: { + double wait; + + if (!h->cfg) + error("Missing configuration for hook: '%s'", plugin_name(h->_vt)); + + if (!config_setting_lookup_float(h->cfg, "seconds", &wait)) + cerror(h->cfg, "Missing setting 'seconds' for hook '%s'", plugin_name(h->_vt)); private->skip = time_from_double(wait); break; + } case HOOK_PATH_START: case HOOK_PATH_RESTART: diff --git a/lib/hooks/stats.c b/lib/hooks/stats.c index 52d8aef6f..5ec1e52fc 100644 --- a/lib/hooks/stats.c +++ b/lib/hooks/stats.c @@ -63,12 +63,17 @@ static int hook_stats_send(struct hook *h, int when, struct hook_info *j) assert(j->nodes); assert(j->path); - if (!h->parameter) - error("Missing parameter for hook '%s'", plugin_name(h->_vt)); + if (!h->cfg) + error("Missing configuration for hook '%s'", plugin_name(h->_vt)); - private->dest = list_lookup(j->nodes, h->parameter); + const char *dest; + + if (!config_setting_lookup_string(h->cfg, "destination", &dest)) + cerror(h->cfg, "Missing setting 'destination' for hook '%s'", plugin_name(h->_vt)); + + private->dest = list_lookup(j->nodes, dest); if (!private->dest) - error("Invalid destination node '%s' for hook '%s'", h->parameter, plugin_name(h->_vt)); + cerror(h->cfg, "Invalid destination node '%s' for hook '%s'", dest, plugin_name(h->_vt)); break; case HOOK_PATH_START: diff --git a/src/hook.c b/src/hook.c index 695bee85e..67c6dfdea 100644 --- a/src/hook.c +++ b/src/hook.c @@ -24,15 +24,46 @@ #include "config.h" +static int hook_parse_cli(struct hook *h, char *param) +{ + int ret; + + config_t cfg; + config_setting_t *cfg_root; + + config_init(&cfg); + config_set_auto_convert(&cfg, 1); + + cfg_root = config_root_setting(&cfg); + + ret = config_read_string(&cfg, param); + if (ret != CONFIG_TRUE) + error("Failed to parse argument '%s': %s", param, config_error_text(&cfg)); + + config_write(&cfg, stdout); + + ret = hook_parse(h, cfg_root); + if (ret) + error("Failed to parse hook settings"); + + config_destroy(&cfg); + + return 0; +} + static void usage() { - printf("Usage: villas-hook [OPTIONS] NAME [PARAMETER] \n"); + printf("Usage: villas-hook [OPTIONS] NAME [PARAMETER1 PARAMTER2 ...] \n"); printf(" NAME the name of the hook function to run\n"); printf(" PARAM the name of the node to which samples are sent and received from\n\n"); printf(" OPTIONS are:\n"); printf(" -h show this help\n"); printf(" -d LVL set debug level to LVL\n"); - printf(" -v CNT process CNT samples at once\n\n"); + printf(" -v CNT process CNT samples at once\n"); + printf("\n"); + printf("Example:"); + printf(" villas-signal random | villas-hook skip_first seconds=10\n"); + printf("\n"); print_copyright(); } @@ -45,7 +76,7 @@ int main(int argc, char *argv[]) level = V; cnt = 1; - char *name, *parameter; + char *name; struct log log; struct plugin *p; @@ -86,17 +117,16 @@ int main(int argc, char *argv[]) error("Failed to initilize memory pool"); name = argv[optind]; - parameter = argc >= optind + 2 ? argv[optind + 1] : NULL; - - h.parameter = parameter; p = plugin_lookup(PLUGIN_TYPE_HOOK, name); if (!p) error("Unknown hook function '%s'", argv[optind]); hook_init(&h, &p->hook, NULL); + + if (argc > optind + 1) + hook_parse_cli(&h, argv[optind + 1]); - hook_run(&h, HOOK_PARSE, &hi); hook_run(&h, HOOK_PATH_START, &hi); while (!feof(stdin)) {