From 9c37348d1dbe0c0fdb22b1574871837430a1bcce Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sat, 25 Mar 2017 21:11:52 +0100 Subject: [PATCH] =?UTF-8?q?added=20new=20hook=20function=20=E2=80=9Emap?= =?UTF-8?q?=E2=80=9C=20to=20remap=20values=20and=20add=20other=20special?= =?UTF-8?q?=20values=20to=20the=20sample=20(fixes=20#80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/villas/mapping.h | 83 ++++++++++ lib/Makefile.inc | 2 +- lib/hooks/map.c | 90 +++++++++++ lib/mapping.c | 320 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 494 insertions(+), 1 deletion(-) create mode 100644 include/villas/mapping.h create mode 100644 lib/hooks/map.c create mode 100644 lib/mapping.c diff --git a/include/villas/mapping.h b/include/villas/mapping.h new file mode 100644 index 000000000..c468cb4bd --- /dev/null +++ b/include/villas/mapping.h @@ -0,0 +1,83 @@ +/** Sample value remapping for mux. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#pragma once + +#include + +#include "stats.h" +#include "common.h" +#include "list.h" + +/* Forward declarations */ +struct stats; +struct node; +struct sample; +struct list; + +struct mapping_entry { + struct node *source; /**< Unused for now. */ + int length; + + enum { + MAPPING_TYPE_DATA, + MAPPING_TYPE_STATS, + MAPPING_TYPE_HEADER, + MAPPING_TYPE_TIMESTAMP + } type; + + union { + struct { + int offset; + } data; + struct { + enum stats_id id; + enum stats_type { + MAPPING_STATS_TYPE_LAST, + MAPPING_STATS_TYPE_HIGHEST, + MAPPING_STATS_TYPE_LOWEST, + MAPPING_STATS_TYPE_MEAN, + MAPPING_STATS_TYPE_VAR, + MAPPING_STATS_TYPE_STDDEV, + MAPPING_STATS_TYPE_TOTAL + } type; + } stats; + struct { + enum header_type { + MAPPING_HEADER_LENGTH, + MAPPING_HEADER_SEQUENCE, + } id; + } header; + struct { + enum timestamp_type { + MAPPING_TIMESTAMP_ORIGIN, + MAPPING_TIMESTAMP_RECEIVED + } id; + } timestamp; + }; +}; + +struct mapping { + enum state state; + + int real_length; + + struct list entries; +}; + +int mapping_init(struct mapping *m); + +int mapping_parse(struct mapping *m, config_setting_t *cfg); + +int mapping_check(struct mapping *m); + +int mapping_destroy(struct mapping *m); + +int mapping_remap(struct mapping *m, struct sample *orig, struct sample *remapped, struct stats *s); + +int mapping_entry_parse(struct mapping_entry *e, config_setting_t *cfg); + +int mapping_entry_parse_str(struct mapping_entry *e, const char *str); \ No newline at end of file diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 84ae00d09..f0f8ec112 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -7,7 +7,7 @@ LIB_SRCS = $(addprefix lib/nodes/, file.c cbuilder.c) \ $(addprefix lib/, sample.c path.c node.c hook.c \ log.c utils.c super_node.c hist.c timing.c pool.c \ list.c queue.c memory.c advio.c web.c api.c \ - plugin.c node_type.c stats.c \ + plugin.c node_type.c stats.c mapping.c \ ) LIB_CFLAGS = $(CFLAGS) -fPIC diff --git a/lib/hooks/map.c b/lib/hooks/map.c new file mode 100644 index 000000000..e5e82b30e --- /dev/null +++ b/lib/hooks/map.c @@ -0,0 +1,90 @@ +/** Override a value of the sample with a header, timestamp or statistic value. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +/** @addtogroup hooks Hook functions + * @{ + */ + +#include "plugin.h" +#include "mapping.h" +#include "list.h" +#include "utils.h" +#include "path.h" + +struct map { + struct mapping mapping; + + struct stats *stats; +}; + +static int hook_map(struct hook *h, int when, struct sample *smps[], size_t *cnt) +{ + int ret; + struct map *p = (struct map *) h->_vd; + + switch (when) { + case HOOK_INIT: + mapping_init(&p->mapping); + break; + + case HOOK_DESTROY: + mapping_destroy(&p->mapping); + break; + + case HOOK_PARSE: { + config_setting_t *cfg_mapping; + + cfg_mapping = config_setting_lookup(h->cfg, "mapping"); + + if (!config_setting_is_array(cfg_mapping)) + return -1; + + ret = mapping_parse(&p->mapping, cfg_mapping); + if (ret) + return ret; + + break; + } + + case HOOK_READ: { + struct sample *tmp[*cnt]; + + if (*cnt <= 0) + return 0; + + ret = sample_alloc(smps[0]->pool, tmp, *cnt); + if (ret != *cnt) + return ret; + + for (int i = 0; i < *cnt; i++) { + mapping_remap(&p->mapping, smps[i], tmp[i], NULL); + + SWAP(smps[i], tmp[i]); + } + + sample_free(tmp, *cnt); + break; + } + } + + return 0; +} + +static struct plugin p = { + .name = "map", + .description = "Remap values and / or add data generated by this instace.", + .type = PLUGIN_TYPE_HOOK, + .hook = { + .priority = 99, + .size = sizeof(struct map), + .cb = hook_map, + .when = HOOK_STORAGE | HOOK_READ | HOOK_PARSE + } +}; + +REGISTER_PLUGIN(&p); + +/** @} */ \ No newline at end of file diff --git a/lib/mapping.c b/lib/mapping.c new file mode 100644 index 000000000..14caac235 --- /dev/null +++ b/lib/mapping.c @@ -0,0 +1,320 @@ +/** Sample value remapping for mux. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#include + +#include "mapping.h" +#include "stats.h" +#include "sample.h" +#include "list.h" +#include "utils.h" + +int mapping_entry_parse_str(struct mapping_entry *e, const char *str) +{ + char *cpy, *type, *field, *subfield, *end; + + cpy = strdup(str); + if (!cpy) + return -1; + + type = strtok(cpy, ".["); + if (!type) + goto invalid_format; + + if (!strcmp(type, "stats")) { + e->type = MAPPING_TYPE_STATS; + e->length = 1; + + field = strtok(NULL, "."); + if (!field) + goto invalid_format; + + subfield = strtok(NULL, "."); + if (!subfield) + goto invalid_format; + + end = strtok(NULL, "."); + if (end) + goto invalid_format; + + e->stats.id = stats_lookup_id(field); + if (e->stats.id < 0) + goto invalid_format; + + if (!strcmp(subfield, "total")) + e->stats.type = MAPPING_STATS_TYPE_TOTAL; + else if (!strcmp(subfield, "last")) + e->stats.type = MAPPING_STATS_TYPE_LAST; + else if (!strcmp(subfield, "lowest")) + e->stats.type = MAPPING_STATS_TYPE_LOWEST; + else if (!strcmp(subfield, "highest")) + e->stats.type = MAPPING_STATS_TYPE_HIGHEST; + else if (!strcmp(subfield, "mean")) + e->stats.type = MAPPING_STATS_TYPE_MEAN; + else if (!strcmp(subfield, "var")) + e->stats.type = MAPPING_STATS_TYPE_VAR; + else if (!strcmp(subfield, "stddev")) + e->stats.type = MAPPING_STATS_TYPE_STDDEV; + else + goto invalid_format; + } + else if (!strcmp(type, "hdr")) { + e->type = MAPPING_TYPE_HEADER; + e->length = 1; + + field = strtok(NULL, "."); + if (!field) + goto invalid_format; + + end = strtok(NULL, "."); + if (end) + goto invalid_format; + + if (!strcmp(field, "sequence")) + e->header.id = MAPPING_HEADER_SEQUENCE; + else if (!strcmp(field, "length")) + e->header.id = MAPPING_HEADER_LENGTH; + else + goto invalid_format; + } + else if (!strcmp(type, "ts")) { + e->type = MAPPING_TYPE_TIMESTAMP; + e->length = 2; + + field = strtok(NULL, "."); + if (!field) + goto invalid_format; + + end = strtok(NULL, "."); + if (end) + goto invalid_format; + + if (!strcmp(field, "origin")) + e->timestamp.id = MAPPING_TIMESTAMP_ORIGIN; + else if (!strcmp(field, "received")) + e->timestamp.id = MAPPING_TIMESTAMP_RECEIVED; + else + goto invalid_format; + } + else if (!strcmp(type, "data")) { + char *first_str, *last_str, *endptr; + int first, last; + + e->type = MAPPING_TYPE_DATA; + + first_str = strtok(NULL, "-]"); + if (!first_str) + goto invalid_format; + + last_str = strtok(NULL, "]"); + if (!last_str) + last_str = first_str; /* single element: data[5] => data[5-5] */ + + end = strtok(NULL, "."); + if (end) + goto invalid_format; + + first = strtoul(first_str, &endptr, 10); + if (endptr != first_str + strlen(first_str)) + goto invalid_format; + + last = strtoul(last_str, &endptr, 10); + if (endptr != last_str + strlen(last_str)) + goto invalid_format; + + if (last < first) + goto invalid_format; + + e->data.offset = first; + e->length = last - first + 1; + } + else + goto invalid_format; + + free(cpy); + return 0; + +invalid_format: + + free(cpy); + return -1; +} + +int mapping_entry_parse(struct mapping_entry *e, config_setting_t *cfg) +{ + const char *str; + + str = config_setting_get_string(cfg); + if (!str) + return -1; + + return mapping_entry_parse_str(e, str); +} + +int mapping_init(struct mapping *m) +{ + assert(m->state == STATE_DESTROYED); + + list_init(&m->entries); + + m->state = STATE_INITIALIZED; + + return 0; +} + +int mapping_destroy(struct mapping *m) +{ + assert(m->state != STATE_DESTROYED); + + list_destroy(&m->entries, NULL, true); + + m->state = STATE_DESTROYED; + + return 0; +} + +int mapping_parse(struct mapping *m, config_setting_t *cfg) +{ + int ret; + + assert(m->state == STATE_INITIALIZED); + + if (!config_setting_is_array(cfg)) + return -1; + + m->real_length = 0; + + for (int i = 0; i < config_setting_length(cfg); i++) { + struct mapping_entry e; + config_setting_t *cfg_mapping; + + cfg_mapping = config_setting_get_elem(cfg, i); + if (!cfg_mapping) + return -1; + + ret = mapping_entry_parse(&e, cfg_mapping); + if (ret) + return ret; + + list_push(&m->entries, memdup(&e, sizeof(e))); + + m->real_length += e.length; + } + + m->state = STATE_PARSED; + + return 0; +} + +int mapping_remap(struct mapping *m, struct sample *original, struct sample *remapped, struct stats *s) +{ + int k = 0; + + if (remapped->capacity < m->real_length) + return -1; + + /* We copy all the header fields */ + remapped->sequence = original->sequence; + remapped->pool = original->pool; + remapped->source = original->source; + remapped->ts = original->ts; + remapped->format = 0; + remapped->length = 0; + + for (size_t i = 0; i < list_length(&m->entries); i++) { + struct mapping_entry *e = list_at(&m->entries, i); + + switch (e->type) { + case MAPPING_TYPE_STATS: { + struct hist *h = &s->histograms[e->stats.id]; + + switch (e->stats.type) { + case MAPPING_STATS_TYPE_TOTAL: + sample_set_data_format(remapped, k, SAMPLE_DATA_FORMAT_INT); + remapped->data[k++].f = h->total; + break; + case MAPPING_STATS_TYPE_LAST: + remapped->data[k++].f = h->last; + break; + case MAPPING_STATS_TYPE_HIGHEST: + remapped->data[k++].f = h->highest; + break; + case MAPPING_STATS_TYPE_LOWEST: + remapped->data[k++].f = h->lowest; + break; + case MAPPING_STATS_TYPE_MEAN: + remapped->data[k++].f = hist_mean(h); + break; + case MAPPING_STATS_TYPE_STDDEV: + remapped->data[k++].f = hist_stddev(h); + break; + case MAPPING_STATS_TYPE_VAR: + remapped->data[k++].f = hist_var(h); + break; + default: + return -1; + } + } + + case MAPPING_TYPE_TIMESTAMP: { + struct timespec *ts; + + switch (e->timestamp.id) { + case MAPPING_TIMESTAMP_RECEIVED: + ts = &original->ts.received; + break; + case MAPPING_TIMESTAMP_ORIGIN: + ts = &original->ts.origin; + break; + default: + return -1; + } + + sample_set_data_format(remapped, k, SAMPLE_DATA_FORMAT_INT); + sample_set_data_format(remapped, k+1, SAMPLE_DATA_FORMAT_INT); + + remapped->data[k++].i = ts->tv_sec; + remapped->data[k++].i = ts->tv_nsec; + + break; + } + + case MAPPING_TYPE_HEADER: + sample_set_data_format(remapped, k, SAMPLE_DATA_FORMAT_INT); + + switch (e->header.id) { + case MAPPING_HEADER_LENGTH: + remapped->data[k++].i = original->length; + break; + case MAPPING_HEADER_SEQUENCE: + remapped->data[k++].i = original->sequence; + break; + default: + return -1; + } + break; + + case MAPPING_TYPE_DATA: + for (int j = e->data.offset; j < e->length + e->data.offset; j++) { + int f = sample_get_data_format(original, j); + + if (j >= original->length) { + sample_set_data_format(remapped, k, SAMPLE_DATA_FORMAT_FLOAT); + remapped->data[k++].f = 0; + } + else { + sample_set_data_format(remapped, k, f); + remapped->data[k++] = original->data[j]; + } + } + break; + } + + remapped->length += e->length; + } + + return 0; +} \ No newline at end of file