From aba93626507d813a9b09ed79e784f4a368743bdb Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 28 Sep 2015 21:32:04 +0200 Subject: [PATCH 01/81] added debug message about request time --- server/src/ngsi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/src/ngsi.c b/server/src/ngsi.c index a08293c43..c66615f70 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -89,7 +89,7 @@ static size_t ngsi_request_writer(void *contents, size_t size, size_t nmemb, voi static int ngsi_request(CURL *handle, json_t *content, json_t **response) { struct ngsi_response chunk = { 0 }; - long code; + char *post = json_dumps(content, JSON_INDENT(4)); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ngsi_request_writer); @@ -103,8 +103,12 @@ static int ngsi_request(CURL *handle, json_t *content, json_t **response) if (ret) error("HTTP request failed: %s", curl_easy_strerror(ret)); + long code; + double time; + curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &time); curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &code); + debug(3, "Request to context broker completed in %.4f seconds", time); debug(20, "Response from context broker (code=%ld):\n%s", code, chunk.data); json_error_t err; From c25030f373d8003ae8d80477e9cacd4f8c2a00bc Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 30 Sep 2015 11:10:29 +0200 Subject: [PATCH 02/81] reset log for tools --- server/src/receive.c | 2 ++ server/src/send.c | 2 ++ server/src/test.c | 1 + 3 files changed, 5 insertions(+) diff --git a/server/src/receive.c b/server/src/receive.c index edf9e45ff..6b4a5d9da 100644 --- a/server/src/receive.c +++ b/server/src/receive.c @@ -89,6 +89,8 @@ int main(int argc, char *argv[]) sigaction(SIGINT, &sa_quit, NULL); list_init(&nodes, (dtor_cb_t) node_destroy); + + log_reset(); config_init(&config); config_parse(argv[optind], &config, &set, &nodes, NULL); diff --git a/server/src/send.c b/server/src/send.c index 833715e1d..2346016ae 100644 --- a/server/src/send.c +++ b/server/src/send.c @@ -89,6 +89,8 @@ int main(int argc, char *argv[]) sigaction(SIGINT, &sa_quit, NULL); list_init(&nodes, (dtor_cb_t) node_destroy); + + log_reset(); config_init(&config); config_parse(argv[optind], &config, &set, &nodes, NULL); diff --git a/server/src/test.c b/server/src/test.c index f20db8798..043db82d6 100644 --- a/server/src/test.c +++ b/server/src/test.c @@ -88,6 +88,7 @@ int main(int argc, char *argv[]) sigaction(SIGTERM, &sa_quit, NULL); sigaction(SIGINT, &sa_quit, NULL); + log_reset(); config_init(&config); config_parse(argv[1], &config, &settings, &nodes, NULL); From 901ca4fbb0c4948b40409a345a0fabf178b6fe80 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 30 Sep 2015 11:12:16 +0200 Subject: [PATCH 03/81] moved checks to where they really belong. improved warnings about non-optimal system --- server/src/gtfpga.c | 3 +++ server/src/if.c | 8 +++++--- server/src/server.c | 21 +++++++++++---------- server/src/socket.c | 4 ++++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/server/src/gtfpga.c b/server/src/gtfpga.c index c763dd68d..72daded3a 100644 --- a/server/src/gtfpga.c +++ b/server/src/gtfpga.c @@ -31,6 +31,9 @@ static void gtfpga_debug(char *msg, ...) { int gtfpga_init(int argc, char * argv[], struct settings *set) { + if (check_root()) + error("The gtfpga node-type requires superuser privileges!"); + pacc = pci_alloc(); /* Get the pci_access structure */ if (!pacc) error("Failed to allocate PCI access structure"); diff --git a/server/src/if.c b/server/src/if.c index 03687a793..18e839d92 100644 --- a/server/src/if.c +++ b/server/src/if.c @@ -34,7 +34,11 @@ struct interface * if_create(struct rtnl_link *link) debug(3, "Created interface '%s'", rtnl_link_get_name(i->nl_link)); - if_get_irqs(i); + int n = if_get_irqs(i); + if (n > 0) + debug(6, "Found %u IRQs for interface '%s'", n, rtnl_link_get_name(i->nl_link)); + else + warn("Did not found any interrupts for interface '%s'", rtnl_link_get_name(i->nl_link)); list_init(&i->sockets, NULL); list_push(&interfaces, i); @@ -177,8 +181,6 @@ int if_get_irqs(struct interface *i) closedir(dir); } - - debug(6, "Found %u IRQs for interface '%s'", n, rtnl_link_get_name(i->nl_link)); return 0; } diff --git a/server/src/server.c b/server/src/server.c index 589a8582f..5c5f29e7f 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -60,6 +60,11 @@ static void quit() static void realtime_init() { INDENT + if (check_kernel_cmdline()) + warn("You should reserve some cores for the server (see 'isolcpus')"); + if (check_kernel_rtpreempt()) + warn("We recommend to use an RT_PREEMPT patched kernel!"); + /* Use FIFO scheduler with real time priority */ if (settings.priority) { struct sched_param param = { @@ -68,18 +73,20 @@ static void realtime_init() if (sched_setscheduler(0, SCHED_FIFO, ¶m)) serror("Failed to set real time priority"); - else - debug(3, "Set task priority to %u", settings.priority); + + debug(3, "Set task priority to %u", settings.priority); } + warn("Use setting 'priority' to enable real-time scheduling!"); /* Pin threads to CPUs by setting the affinity */ if (settings.affinity) { cpu_set_t cset = to_cpu_set(settings.affinity); if (sched_setaffinity(0, sizeof(cset), &cset)) serror("Failed to set CPU affinity to '%#x'", settings.affinity); - else - debug(3, "Set affinity to %#x", settings.affinity); + + debug(3, "Set affinity to %#x", settings.affinity); } + warn("Use setting 'affinity' to pin process to isolated CPU cores!"); } /* Setup exit handler */ @@ -149,8 +156,6 @@ int main(int argc, char *argv[]) BLD(MAG(__DATE__)), BLD(MAG(__TIME__))); /* Checks system requirements*/ - if (check_root()) - error("The server requires superuser privileges!"); #ifdef LICENSE if (check_license_trace()) error("This software should not be traced!"); @@ -161,10 +166,6 @@ int main(int argc, char *argv[]) #endif if (check_kernel_version()) error("Your kernel version is to old: required >= %u.%u", KERNEL_VERSION_MAJ, KERNEL_VERSION_MIN); - if (check_kernel_cmdline()) - warn("You should reserve some cores for the server (see 'isolcpus')"); - if (check_kernel_rtpreempt()) - warn("We recommend to use an RT_PREEMPT patched kernel!"); /* Initialize lists */ list_init(&nodes, (dtor_cb_t) node_destroy); diff --git a/server/src/socket.c b/server/src/socket.c index 5e1a5946b..8d6e1a0ed 100644 --- a/server/src/socket.c +++ b/server/src/socket.c @@ -30,6 +30,7 @@ #include "config.h" #include "utils.h" #include "socket.h" +#include "checks.h" /** Linked list of interfaces */ extern struct list interfaces; @@ -39,6 +40,9 @@ static struct list sockets; int socket_init(int argc, char * argv[], struct settings *set) { INDENT + if (check_root()) + error("The socket node-type requires superuser privileges!"); + nl_init(); /* Fill link cache */ list_init(&interfaces, (dtor_cb_t) if_destroy); From 4df5bc0c6112d9fe4f1d447e6880f207f5ec03c1 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 30 Sep 2015 11:12:49 +0200 Subject: [PATCH 04/81] rewrite of RTT test by using new message format --- server/src/test.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/server/src/test.c b/server/src/test.c index 043db82d6..3864e98de 100644 --- a/server/src/test.c +++ b/server/src/test.c @@ -154,11 +154,8 @@ check: void test_rtt() { struct msg m = MSG_INIT(sizeof(struct timespec) / sizeof(float)); - struct timespec ts; - struct timespec *ts1 = (struct timespec *) &m.data; - struct timespec *ts2 = alloc(sizeof(struct timespec)); + struct timespec sent, recv; - double rtt; struct hist hist; hist_create(&hist, low, high, res); @@ -166,33 +163,30 @@ void test_rtt() { fprintf(stdout, "%17s%5s%10s%10s%10s%10s%10s\n", "timestamp", "seq", "rtt", "min", "max", "mean", "stddev"); while (running && (count < 0 || count--)) { - clock_gettime(CLOCK_ID, ts1); + clock_gettime(CLOCK_ID, &sent); + m.ts.sec = sent.tv_sec; + m.ts.nsec = sent.tv_nsec; + node_write_single(node, &m); /* Ping */ node_read_single(node, &m); /* Pong */ - clock_gettime(CLOCK_ID, ts2); + + clock_gettime(CLOCK_ID, &recv); - rtt = time_delta(ts1, ts2); + double rtt = time_delta(&recv, &sent); if (rtt < 0) warn("Negative RTT: %f", rtt); hist_put(&hist, rtt); - clock_gettime(CLOCK_REALTIME, &ts); - time_fprint(stdout, &ts); - m.sequence++; - fprintf(stdout, "%5u%10.3f%10.3f%10.3f%10.3f%10.3f\n", m.sequence, + fprintf(stdout, "%10lu.%06lu%5u%10.3f%10.3f%10.3f%10.3f%10.3f\n", + recv.tv_sec, recv.tv_nsec / 1000, m.sequence, 1e3 * rtt, 1e3 * hist.lowest, 1e3 * hist.highest, 1e3 * hist_mean(&hist), 1e3 * hist_stddev(&hist)); } - /* Housekeeping */ - free(ts2); - - hist_print(&hist); - struct stat st; if (!fstat(fd, &st)) { FILE *f = fdopen(fd, "w"); @@ -201,5 +195,6 @@ void test_rtt() { else error("Invalid file descriptor: %u", fd); + hist_print(&hist); hist_destroy(&hist); } From 3ca3853e11be9db58f1b2e6a482f2bcce43f9cb2 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 6 Oct 2015 09:02:00 +0200 Subject: [PATCH 05/81] cleanups: struct config_setting_t => config_setting_t --- server/src/cfg.c | 6 +++--- server/src/receive.c | 2 +- server/src/send.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/cfg.c b/server/src/cfg.c index a16d9a227..18db5fa85 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -102,7 +102,7 @@ int config_parse_path(config_setting_t *cfg, struct path *p = path_create(); /* Input node */ - struct config_setting_t *cfg_in = config_setting_get_member(cfg, "in"); + config_setting_t *cfg_in = config_setting_get_member(cfg, "in"); if (!cfg_in || config_setting_type(cfg_in) != CONFIG_TYPE_STRING) cerror(cfg, "Invalid input node for path"); @@ -112,7 +112,7 @@ int config_parse_path(config_setting_t *cfg, cerror(cfg_in, "Invalid input node '%s'", in); /* Output node(s) */ - struct config_setting_t *cfg_out = config_setting_get_member(cfg, "out"); + config_setting_t *cfg_out = config_setting_get_member(cfg, "out"); if (cfg_out) config_parse_nodelist(cfg_out, &p->destinations, nodes); @@ -122,7 +122,7 @@ int config_parse_path(config_setting_t *cfg, cerror(cfg, "Missing output node for path"); /* Optional settings */ - struct config_setting_t *cfg_hook = config_setting_get_member(cfg, "hook"); + config_setting_t *cfg_hook = config_setting_get_member(cfg, "hook"); if (cfg_hook) config_parse_hooklist(cfg_hook, p->hooks); diff --git a/server/src/receive.c b/server/src/receive.c index 6b4a5d9da..66021f52a 100644 --- a/server/src/receive.c +++ b/server/src/receive.c @@ -62,7 +62,7 @@ int main(int argc, char *argv[]) { int reverse = 0; - struct config_t config; + config_t config; _mtid = pthread_self(); diff --git a/server/src/send.c b/server/src/send.c index 2346016ae..1a658d83e 100644 --- a/server/src/send.c +++ b/server/src/send.c @@ -28,7 +28,7 @@ struct list nodes; /** The global configuration */ struct settings settings; -static struct config_t config; +static config_t config; static struct settings set; static struct msg *pool; static struct node *node; From 0485ffe4536aad25ef68395c6b11443048410230 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 6 Oct 2015 09:10:08 +0200 Subject: [PATCH 06/81] code cleanups: code convention --- server/src/opal.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/opal.c b/server/src/opal.c index e0b805ce0..fe36076b7 100644 --- a/server/src/opal.c +++ b/server/src/opal.c @@ -122,9 +122,9 @@ int opal_print_global(struct opal_global *g) free(rbuf); debug(2, "Control Block Parameters:"); - for (int i=0; iparams.FloatParam[i]); - for (int i=0; iparams.StringParam[i]); return 0; @@ -174,7 +174,7 @@ int opal_open(struct node *n) int sfound = 0, rfound = 0; for (int i = 0; i < og->send_icons; i++) sfound += og->send_ids[i] == o->send_id; - for (int i = 0; isend_icons; i++) + for (int i = 0; i < og->send_icons; i++) rfound += og->send_ids[i] == o->send_id; if (!sfound) From e9b9e85d642b47e5ab32e8c67c16a400267743f1 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 7 Oct 2015 16:52:23 +0200 Subject: [PATCH 07/81] added missing timestamp metadata to NGSI node type fixes #1 --- server/src/ngsi.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/server/src/ngsi.c b/server/src/ngsi.c index c66615f70..123e9550f 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -194,6 +194,11 @@ void ngsi_prepare_context(struct node *n, config_setting_t *mapping) "type", "integer", "value", j )); + json_array_append(metadatas, json_pack("{ s: s, s: s, s: o }", + "name", "timestamp", + "type", "date", + "value", json_date(NULL) + )); if (i->structure == NGSI_CHILDREN) { json_array_append(attributes, json_pack("{ s: s, s: s, s: s }", @@ -339,18 +344,17 @@ int ngsi_write(struct node *n, struct msg *pool, int poolsize, int first, int cn /* Update context */ for (int j = 0; j < MIN(i->context_len, m->length); j++) { json_t *attribute = i->context_map[j]; - json_t *value = json_object_get(attribute, "value"); json_t *metadatas = json_object_get(attribute, "metadatas"); + + /* Update timestamp */ + json_t *metadata_ts = json_lookup(metadatas, "name", "timestamp"); + json_object_set(metadata_ts, "value", json_date(&MSG_TS(m))); - json_t *timestamp = json_lookup(metadatas, "name", "timestamp"); - json_object_update(timestamp, json_pack("{ s: s, s: s, s: o }", - "name", "timestamp", - "type", "date", - "value", json_date(&MSG_TS(m)) - )); - + /* Update value */ char new[64]; snprintf(new, sizeof(new), "%f", m->data[j].f); /** @todo for now we only support floating point values */ + + json_t *value = json_object_get(attribute, "value"); json_string_set(value, new); } From 741802336e9c38d23c98d051ccddec5f60095688 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 7 Oct 2015 16:54:56 +0200 Subject: [PATCH 08/81] adjusted debug level of NGSI request time --- server/src/ngsi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/ngsi.c b/server/src/ngsi.c index 123e9550f..3512d480b 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -108,7 +108,7 @@ static int ngsi_request(CURL *handle, json_t *content, json_t **response) curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &time); curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &code); - debug(3, "Request to context broker completed in %.4f seconds", time); + debug(20, "Request to context broker completed in %.4f seconds", time); debug(20, "Response from context broker (code=%ld):\n%s", code, chunk.data); json_error_t err; From 84425199c9685b999f129cce35c33b6328c645b8 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 7 Oct 2015 09:12:56 +0200 Subject: [PATCH 09/81] added strdup() alike helper to clone objects --- server/include/utils.h | 3 +++ server/src/utils.c | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/server/include/utils.h b/server/include/utils.h index 15a5a5c20..4aa77b72d 100644 --- a/server/include/utils.h +++ b/server/include/utils.h @@ -111,6 +111,9 @@ cpu_set_t to_cpu_set(int set); /** Allocate and initialize memory. */ void * alloc(size_t bytes); +/** Allocate and copy memory. */ +void * memdup(const void *src, size_t bytes); + /** Call quit() in the main thread. */ void die(); diff --git a/server/src/utils.c b/server/src/utils.c index 4e53e581a..aab070f58 100644 --- a/server/src/utils.c +++ b/server/src/utils.c @@ -135,3 +135,12 @@ void * alloc(size_t bytes) return p; } + +void * memdup(const void *src, size_t bytes) +{ + void *dst = alloc(bytes); + + memcpy(dst, src, bytes); + + return dst; +} \ No newline at end of file From 7682ffb080e62e2321f867b510931ab59151259e Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 7 Oct 2015 18:12:12 +0200 Subject: [PATCH 10/81] Changed format of msg_{fscan,print} functions Added flags and options to the mentioned functions --- server/include/msg.h | 26 +++++++--- server/src/file.c | 10 ++-- server/src/hooks.c | 3 +- server/src/msg.c | 120 +++++++++++++++++++++++++++++++++---------- server/src/random.c | 4 +- server/src/receive.c | 13 +++-- server/src/send.c | 23 ++++++--- 7 files changed, 149 insertions(+), 50 deletions(-) diff --git a/server/include/msg.h b/server/include/msg.h index 949cf52d3..0bb043c81 100644 --- a/server/include/msg.h +++ b/server/include/msg.h @@ -16,6 +16,16 @@ struct node; +/** These flags define the format which is used by msg_fscan() and msg_fprint(). */ +enum msg_flags { + MSG_PRINT_NANOSECONDS = 1, + MSG_PRINT_OFFSET = 2, + MSG_PRINT_SEQUENCE = 4, + MSG_PRINT_VALUES = 8, + + MSG_PRINT_ALL = 0xFF +}; + /** Swap a message to host byte order. * * Message can either be transmitted in little or big endian @@ -41,21 +51,25 @@ int msg_verify(struct msg *m); /** Print a raw message in human readable form to a file stream. * - * @param f The file stream - * @param m A pointer to the message + * @param f The file handle from fopen() or stdout, stderr. + * @param m A pointer to the message. + * @param flags See msg_flags. + * @param offset A optional offset in seconds. Only used if flags contains MSG_PRINT_OFFSET. * @retval 0 Success. Everything went well. * @retval <0 Error. Something went wrong. */ -int msg_fprint(FILE *f, struct msg *m); +int msg_fprint(FILE *f, struct msg *m, int flags, double offset); /** Read a message from a file stream. * - * @param f The file stream - * @param m A pointer to the message + * @param f The file handle from fopen() or stdin. + * @param m A pointer to the message. + * @param flags et MSG_PRINT_* flags for each component found in sample if not NULL. See msg_flags. + * @param offset Write offset to this pointer if not NULL. * @retval 0 Success. Everything went well. * @retval <0 Error. Something went wrong. */ -int msg_fscan(FILE *f, struct msg *m); +int msg_fscan(FILE *f, struct msg *m, int *flags, double *offset); /** Change the values of an existing message in a random fashion. * diff --git a/server/src/file.c b/server/src/file.c index d25028c97..2aab7cf13 100644 --- a/server/src/file.c +++ b/server/src/file.c @@ -185,13 +185,13 @@ int file_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt serror("Failed to wait for timer"); } - msg_fscan(f->in, cur); + msg_fscan(f->in, cur, NULL, NULL); } else { struct timespec until; /* Get message and timestamp */ - msg_fscan(f->in, cur); + msg_fscan(f->in, cur, NULL, NULL); /* Wait for next message / sampe */ until = time_add(&MSG_TS(cur), &f->offset); @@ -215,8 +215,10 @@ int file_write(struct node *n, struct msg *pool, int poolsize, int first, int cn struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); - for (i = 0; i < cnt; i++) - msg_fprint(f->out, &pool[(first+i) % poolsize]); + for (i = 0; i < cnt; i++) { + struct msg *m = &pool[(first+i) % poolsize]; + msg_fprint(f->out, m, MSG_PRINT_ALL & ~MSG_PRINT_OFFSET, 0); + } } else error("Can not write to node '%s", n->name); diff --git a/server/src/hooks.c b/server/src/hooks.c index 9811b9bd1..fa679d418 100644 --- a/server/src/hooks.c +++ b/server/src/hooks.c @@ -59,8 +59,7 @@ int hook_print(struct path *p) struct msg *m = p->current; struct timespec ts = MSG_TS(m); - fprintf(stdout, "%.3e+", time_delta(&ts, &p->ts_recv)); /* Print delay */ - msg_fprint(stdout, m); + msg_fprint(stdout, m, MSG_PRINT_ALL, time_delta(&ts, &p->ts_recv)); return 0; } diff --git a/server/src/msg.c b/server/src/msg.c index b92374e1a..20cf4639e 100644 --- a/server/src/msg.c +++ b/server/src/msg.c @@ -44,15 +44,23 @@ int msg_verify(struct msg *m) return 0; } -int msg_fprint(FILE *f, struct msg *m) +int msg_fprint(FILE *f, struct msg *m, int flags, double offset) { - if (m->endian != MSG_ENDIAN_HOST) - msg_swap(m); + fprintf(f, "%u", m->ts.sec); + + if (flags & MSG_PRINT_NANOSECONDS) + fprintf(f, ".%09u", m->ts.nsec); + + if (flags & MSG_PRINT_OFFSET) + fprintf(f, "%+g", offset); + + if (flags & MSG_PRINT_SEQUENCE) + fprintf(f, "(%u)", m->sequence); - fprintf(f, "%10u.%09u\t%hu", m->ts.sec, m->ts.nsec, m->sequence); - - for (int i = 0; i < m->length; i++) - fprintf(f, "\t%.6f", m->data[i].f); + if (flags & MSG_PRINT_VALUES) { + for (int i = 0; i < m->length; i++) + fprintf(f, "\t%.6f", m->data[i].f); + } fprintf(f, "\n"); @@ -60,33 +68,93 @@ int msg_fprint(FILE *f, struct msg *m) } /** @todo Currently only floating point values are supported */ -int msg_fscan(FILE *f, struct msg *m) +int msg_fscan(FILE *f, struct msg *m, int *fl, double *off) { char line[MSG_VALUES * 16]; - char *next, *ptr = line; - - if (fgets(line, sizeof(line), f) == NULL) - return -1; /* An error occured */ - - m->ts.sec = (uint32_t) strtoul(ptr, &ptr, 10); ptr++; - m->ts.nsec = (uint32_t) strtoul(ptr, &ptr, 10); - m->sequence = (uint16_t) strtoul(ptr, &ptr, 10); - + char *end, *ptr = line; + int flags = 0; + double offset; + m->version = MSG_VERSION; m->endian = MSG_ENDIAN_HOST; - m->length = 0; - m->rsvd1 = 0; - m->rsvd2 = 0; + m->rsvd1 = m->rsvd2 = 0; + + /* Format: Seconds.NanoSeconds+Offset(SequenceNumber) Value1 Value2 ... + * RegEx: (\d+(?:\.\d+)?)([-+]\d+(?:\.\d+)?(?:e[+-]?\d+)?)?(?:\((\d+)\))? + * + * Please note that only the seconds and at least one value are mandatory + */ - while (m->length < MSG_VALUES) { - m->data[m->length].f = strtod(ptr, &next); +skip: if (fgets(line, sizeof(line), f) == NULL) + return -1; /* An error occured */ - if (next == ptr) - break; + if (line[0] == '#') + goto skip; - ptr = next; - m->length++; + /* Mandatory: seconds */ + m->ts.sec = (uint32_t) strtoul(ptr, &end, 10); + if (ptr == end) + return -2; + + /* Optional: nano seconds */ + if (*end == '.') { + ptr = end + 1; + + m->ts.nsec = (uint32_t) strtoul(ptr, &end, 10); + if (ptr != end) + flags |= MSG_PRINT_NANOSECONDS; + else + return -3; } + else + m->ts.nsec = 0; + + /* Optional: offset / delay */ + if (*end == '+' || *end == '-') { + ptr = end + 1; + + offset = strtof(ptr, &end); /* offset is ignored for now */ + if (ptr != end) + flags |= MSG_PRINT_OFFSET; + else + return -4; + } + + /* Optional: sequence number */ + if (*end == '(') { + ptr = end + 1; + + m->sequence = (uint16_t) strtoul(ptr, &end, 10); + if (ptr != end && *end == ')') + flags |= MSG_PRINT_SEQUENCE; + else { + info(end); + return -5; + } + + end = end + 1; + } + else + m->sequence = 0; + + for ( m->length = 0, ptr = end; + m->length < MSG_VALUES; + m->length++, ptr = end) { + + /** @todo We only support floating point values at the moment */ + m->data[m->length].f = strtod(ptr, &end); + + if (end == ptr) /* there are no valid FP values anymore */ + break; + } + + if (m->length > 0) + flags |= MSG_PRINT_VALUES; + + if (fl) + *fl = flags; + if (off && (flags & MSG_PRINT_OFFSET)) + *off = offset; return m->length; } diff --git a/server/src/random.c b/server/src/random.c index 305d88dc0..c253592bc 100644 --- a/server/src/random.c +++ b/server/src/random.c @@ -53,7 +53,7 @@ int main(int argc, char *argv[]) serror("Failed to start timer"); /* Print header */ - fprintf(stderr, "# %-20s\t%s\t%s\n", "timestamp", "seqno", "data[]"); + fprintf(stderr, "# %-20s\t\t%s\n", "sec.nsec(seq)", "data[]"); /* Block until 1/p->rate seconds elapsed */ while (limit-- > 0 || argc < 4) { @@ -66,7 +66,7 @@ int main(int argc, char *argv[]) m.ts.nsec = ts.tv_nsec; msg_random(&m); - msg_fprint(stdout, &m); + msg_fprint(stdout, &m, MSG_PRINT_ALL & ~MSG_PRINT_OFFSET, 0); fflush(stdout); } diff --git a/server/src/receive.c b/server/src/receive.c index 66021f52a..2d612d08a 100644 --- a/server/src/receive.c +++ b/server/src/receive.c @@ -110,16 +110,23 @@ int main(int argc, char *argv[]) pool = alloc(sizeof(struct msg) * node->combine); /* Print header */ - fprintf(stderr, "# %-20s\t%s\t%s\n", "timestamp", "seqno", "data[]"); + fprintf(stderr, "# %-20s\t\t%s\n", "sec.nsec+offset(seq)", "data[]"); for (;;) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + int recv = node_read(node, pool, node->combine, 0, node->combine); for (int i = 0; i < recv; i++) { - int ret = msg_verify(&pool[i]); + struct msg *m = &pool[i]; + + int ret = msg_verify(m); if (ret) warn("Failed to verify message: %d", ret); + + /** @todo should we drop reordered / delayed packets here? */ - msg_fprint(stdout, &pool[i]); + msg_fprint(stdout, &pool[i], MSG_PRINT_ALL, time_delta(&MSG_TS(m), &ts)); } } diff --git a/server/src/send.c b/server/src/send.c index 1a658d83e..7c5e57b85 100644 --- a/server/src/send.c +++ b/server/src/send.c @@ -113,15 +113,24 @@ int main(int argc, char *argv[]) pool = alloc(sizeof(struct msg) * node->combine); /* Print header */ - fprintf(stderr, "# %-20s\t%s\t%s\n", "timestamp", "seqno", "data[]"); + fprintf(stderr, "# %-20s\t\t%s\n", "sec.nsec+offset(seq)", "data[]"); for (;;) { - int i = 0; - while (i < node->combine) { - if (msg_fscan(stdin, &pool[i]) > 0) - msg_fprint(stdout, &pool[i++]); - else if (feof(stdin)) - goto out; + for (int i = 0; i < node->combine; i++) { + struct msg *m = &pool[i]; + int reason; + +retry: reason = msg_fscan(stdin, m, NULL, NULL); + if (reason < 0) { + if (feof(stdin)) + goto out; + else { + warn("Skipped invalid message message: reason=%d", reason); + goto retry; + } + } + else + msg_fprint(stdout, m, MSG_PRINT_ALL, 0); } node_write(node, pool, node->combine, 0, node->combine); From 38bee64e61a2acecdbc00fb95debaf2250524d79 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 7 Oct 2015 19:50:03 +0200 Subject: [PATCH 11/81] added missing configuration parameters for paths --- documentation/Configuration.md | 63 ++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/documentation/Configuration.md b/documentation/Configuration.md index 3e5e73c3b..4b7fcb212 100644 --- a/documentation/Configuration.md +++ b/documentation/Configuration.md @@ -9,13 +9,13 @@ The configuration file consists of three sections: The global section consists of some global configuration parameters: -#### `debug` +#### `debug` *(integer)* `debug` expects an integer number (0-10) which determines the verbosity of debug messages during the execution of the server. Use this with care! Producing a lot of IO might decrease the performance of the server. Omitting this setting or setting it to zero will disable debug messages completely. -#### `stats` +#### `stats` *(float)* `stats` specifies the rate in which statistics about the actives paths will be printed to the screen. Setting this value to 5, will print 5 lines per second. @@ -25,19 +25,19 @@ A line of includes information such as: - Messages sent - Messaged dropped -#### `affinity` +#### `affinity` *(integer)* The `affinity` setting allows to restrict the exeuction of the daemon to certain CPU cores. This technique, also called 'pinning', improves the determinism of the server by isolating the daemon processes on exclusive cores. -#### `priority` +#### `priority` *(integer)* The `priority` setting allows to adjust the scheduling priority of the deamon processes. By default, the daemon uses a real-time optimized FIFO scheduling algorithm. ## Nodes -The node section is a **directory** of nodes (clients) which are connected to the S2SS instance. +The node section is a **directory** of nodes (clients) which are connected to the S2SS instance. The directory is indexed by the name of the node: nodes = { @@ -49,7 +49,7 @@ The directory is indexed by the name of the node: There are multiple diffrent type of nodes. But all types have the following settings in common: -#### `type` +#### `type` *("socket" | "gtfpga" | "file" | "ngsi")* `type` sets the type of the node. This should be one of: - `socket` which refers to a [Socket](socket) node. @@ -62,16 +62,65 @@ Take a look a the specific pages for details. ## Paths -The path section consists of a **list** of paths. +The path section consists of a **list** of paths: + + paths = [ + { + in = "rtds", + out = "broker", + reverse = false, + poolsize = 32, + msgsize = 16, + combine = 4, + hook = [ "print", "ts" ] + } + ] + Every path is allowed to have the following settings: +##### `in` & `out` *(string)* + The `in` and `out` settings expect the name of the source and destination node. + The `out` setting itself is allowed to be list of nodes. This enables 1-to-n distribution of simulation data. +##### `enabled` *(boolean)* + The optional `enabled` setting can be used to temporarily disable a path. If omitted, the path is enabled by default. +##### `reverse` *(boolean)* + By default, the path is unidirectional. Meaning, that it only forwards samples from the source to the destination. Sometimes a bidirectional path is needed. This can be accomplished by setting `reverse` to `true`. + +##### `combine` *(integer)* + +This setting allows to send multiple samples in a single message to the destination nodes. Currently this is only supported by the `file` and `socket` node-types. + +The value of this setting determines how many samples will be combined into one packet. + +**Important:** Please make sure that the value of this setting is smaller or equal to the `poolsize` setting of this path. + +##### `rate` *(float)* + +A non-zero value for this setting will change this path to an asynchronous mode. +In this mode S2SS will send with a fixed rate to all destination nodes. +It will always send the latest value it received, possible skipping values which have been received in between. +If `combine` is larger than 1, it will send the last `combine` samples at once. + +**Important:** Please note that there is no correlation between the time of arrival and time of departure in this mode. It might increase the latency of this path by up to `1 / rate` seconds! + +##### `poolsize` *(integer)* + +Every path manages a circular buffer to keep a history of past samples. This setting specifies the size of this circular buffer. + +**Important:** There are some hook functions (or the `combine` setting) which require a minimum poolsize (for example the finite-impulse-response `fir` hook). + +##### `hook` *(list of strings)* + +A list of hook functions which will be executed for this path. + +Please consult the hook chapter of this documentation for more details. From 4ba0f17366e5c7e54dde17d3c0a26f21dc61d780 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 8 Oct 2015 10:52:11 +0200 Subject: [PATCH 12/81] Updated documentation on message format for file node-type --- documentation/clients/File.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/documentation/clients/File.md b/documentation/clients/File.md index 2551ec89c..28a14b112 100644 --- a/documentation/clients/File.md +++ b/documentation/clients/File.md @@ -77,15 +77,25 @@ If this setting has a non-zero value, the default behaviour is overwritten with ## File Format -This node-type uses a simple human-readable format to save samples: +This node-type uses a simple human-readable format to save samples to disk: The format is similiar to a conventional CSV (comma seperated values) file. Every line in a file correspondents to a message / sample of simulation data. The columns of a line are seperated by whitespaces (tabs or spaces). The columns are defined as follows: - 1. Seconds in floating point format since 1970-01-01 00:00:00 (aka Unix epoch timestamp: `date +%s`). - 2. Sequence number - 3. Up to `MSG_VALUES` floating point values per sample. The values are seperated by whitespaces as well. + seconds.nanoseconds+offset(sequenceno) value0 value1 ... valueN + + 1. The first column contains a timestamp which is composed of up to 4 parts: + - Number of seconds after 1970-01-01 00:00:00 UTC + - A dot: '.' + - Number of nano seconds of the current second (optional) + - An offset between the point in time when a message was sent and received (optional) + - The sequence number of the message (optional) + + A valid timestamp can be generated by the following Unix command: `date +%s.%N`. + *Important:* The second field is not the fractional part of the second!!! + + 2. Maximum `MSG_VALUES` floating point values per sample. The values are seperated by whitespaces as well. ### Example From 1b9a1abffdf131f2e60d7893a5d21fe24790bf30 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 7 Oct 2015 18:13:16 +0200 Subject: [PATCH 13/81] removed time_{fscan,fprint} functions to avoid code duplication --- server/include/timing.h | 6 ------ server/src/file.c | 4 +++- server/src/timing.c | 12 +----------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/server/include/timing.h b/server/include/timing.h index f8061182b..88711a3b3 100644 --- a/server/include/timing.h +++ b/server/include/timing.h @@ -48,10 +48,4 @@ double time_to_double(struct timespec *ts); /** Convert double containing seconds after 1970 to timespec. */ struct timespec time_from_double(double secs); -/** Read a timestamp from a file with the format: "secs.nanosecs\t" */ -int time_fscan(FILE *f, struct timespec *ts); - -/** Write a timestamp to a file with the format: "secs.nanosecs\t" */ -int time_fprint(FILE *f, struct timespec *ts); - #endif /* _TIMING_H_ */ \ No newline at end of file diff --git a/server/src/file.c b/server/src/file.c index 2aab7cf13..ebee537a3 100644 --- a/server/src/file.c +++ b/server/src/file.c @@ -107,12 +107,14 @@ int file_open(struct node *n) serror("Failed to start timer"); } else { + struct msg m; /* Get current time */ struct timespec now, tmp; clock_gettime(CLOCK_REALTIME, &now); /* Get timestamp of first sample */ - time_fscan(f->in, &f->start); rewind(f->in); + msg_fscan(f->in, &m, NULL, NULL); + first = MSG_TS(&m); /* Set offset depending on epoch_mode */ switch (f->epoch_mode) { diff --git a/server/src/timing.c b/server/src/timing.c index 1e063f4cf..b0ae18a93 100644 --- a/server/src/timing.c +++ b/server/src/timing.c @@ -81,14 +81,4 @@ double time_delta(struct timespec *start, struct timespec *end) struct timespec diff = time_diff(start, end); return time_to_double(&diff); -} - -int time_fscan(FILE *f, struct timespec *ts) -{ - return fscanf(f, "%lu.%lu", &ts->tv_sec, &ts->tv_nsec); -} - -int time_fprint(FILE *f, struct timespec *ts) -{ - return fprintf(f, "%lu.%09lu\t", ts->tv_sec, ts->tv_nsec); -} +} \ No newline at end of file From e5b036e8ff009e5f9fa17316e56c0e2656d36313 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 7 Oct 2015 18:13:40 +0200 Subject: [PATCH 14/81] renamed some variable to ease understanding --- server/src/file.c | 14 +++++++------- server/src/socket.c | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/server/src/file.c b/server/src/file.c index ebee537a3..bb06e2058 100644 --- a/server/src/file.c +++ b/server/src/file.c @@ -109,7 +109,7 @@ int file_open(struct node *n) else { struct msg m; /* Get current time */ - struct timespec now, tmp; + struct timespec now, first, eta; clock_gettime(CLOCK_REALTIME, &now); /* Get timestamp of first sample */ @@ -119,8 +119,8 @@ int file_open(struct node *n) /* Set offset depending on epoch_mode */ switch (f->epoch_mode) { case EPOCH_NOW: /* read first value at f->now + f->epoch */ - tmp = time_diff(&f->start, &now); - f->offset = time_add(&tmp, &f->epoch); + first = time_diff(&f->start, &now); + f->offset = time_add(&first, &f->epoch); break; case EPOCH_RELATIVE: /* read first value at f->start + f->epoch */ f->offset = f->epoch; @@ -130,14 +130,14 @@ int file_open(struct node *n) break; } - tmp = time_add(&f->start, &f->offset); - tmp = time_diff(&now, &tmp); + eta = time_add(&f->start, &f->offset); + eta = time_diff(&now, &eta); - debug(5, "Opened file '%s' as input for node '%s': start=%.2f, offset=%.2f, eta=%.2f", + debug(5, "Opened file '%s' as input for node '%s': start=%.2f, offset=%.2f, eta=%.2f sec", f->path_in, n->name, time_to_double(&f->start), time_to_double(&f->offset), - time_to_double(&tmp) + time_to_double(&eta) ); } } diff --git a/server/src/socket.c b/server/src/socket.c index 8d6e1a0ed..437e13f60 100644 --- a/server/src/socket.c +++ b/server/src/socket.c @@ -241,16 +241,16 @@ int socket_write(struct node *n, struct msg *pool, int poolsize, int first, int struct iovec iov[cnt]; for (int i = 0; i < cnt; i++) { - struct msg *n = &pool[(first+i) % poolsize]; - - if (n->type == MSG_TYPE_EMPTY) + struct msg *m = &pool[(first+i) % poolsize]; + + if (m->type == MSG_TYPE_EMPTY) continue; /* Convert headers to network byte order */ n->sequence = htons(n->sequence); - iov[sent].iov_base = n; - iov[sent].iov_len = MSG_LEN(n); + iov[sent].iov_base = m; + iov[sent].iov_len = MSG_LEN(m); sent++; } From c640a6ba2141b773c361910b415c9a8bac4b7445 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 8 Oct 2015 10:51:48 +0200 Subject: [PATCH 15/81] Changed UDP message format: sequence no, length and timestamps are also affected by struct msg::endian!!! Important: this will break the current GTFPGA code --- .../opal/udp/models/send_receive/include/msg.h | 4 ++-- .../models/send_receive/include/msg_format.h | 12 +++++------- clients/opal/udp/models/send_receive/src/msg.c | 15 ++++++++++----- clients/opal/udp/models/send_receive/src/s2ss.c | 17 +++++++---------- server/include/msg.h | 4 ++-- server/include/msg_format.h | 12 +++++------- server/src/msg.c | 9 +++++++-- server/src/socket.c | 13 +++---------- 8 files changed, 41 insertions(+), 45 deletions(-) diff --git a/clients/opal/udp/models/send_receive/include/msg.h b/clients/opal/udp/models/send_receive/include/msg.h index 70d5ae4b7..efbf9552b 100644 --- a/clients/opal/udp/models/send_receive/include/msg.h +++ b/clients/opal/udp/models/send_receive/include/msg.h @@ -12,11 +12,11 @@ #include "msg_format.h" -/** Swap a message to host byte order. +/** Swaps message contents byte-order. * * Message can either be transmitted in little or big endian * format. The actual endianess for a message is defined by the - * msg::byteorder field. + * msg::endian field. This covers msg::length, msg::sequence, msg::data and msg::ts fields. * Received message are usally converted to the endianess of the host. * This is required for further sanity checks of the sequence number * or parsing of the data. diff --git a/clients/opal/udp/models/send_receive/include/msg_format.h b/clients/opal/udp/models/send_receive/include/msg_format.h index d7b41d6cc..a85bbea5c 100644 --- a/clients/opal/udp/models/send_receive/include/msg_format.h +++ b/clients/opal/udp/models/send_receive/include/msg_format.h @@ -80,18 +80,16 @@ struct msg #endif unsigned rsvd2 : 8; /**< Reserved bits */ - /** The number of values in msg::data[] */ - uint16_t length; - - uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages */ + uint16_t length; /**< The number of values in msg::data[]. Endianess is specified in msg::endian. */ + uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages. Endianess is specified in msg::endian. */ - /** A timestamp per message */ + /** A timestamp per message. Endianess is specified in msg::endian. */ struct { uint32_t sec; /**< Seconds since 1970-01-01 00:00:00 */ - uint32_t nsec; /**< Nanoseconds since 1970-01-01 00:00:00 */ + uint32_t nsec; /**< Nanoseconds of the current second. */ } ts; - /** The message payload */ + /** The message payload. Endianess is specified in msg::endian. */ union { float f; /**< Floating point values (note msg::endian) */ uint32_t i; /**< Integer values (note msg::endian) */ diff --git a/clients/opal/udp/models/send_receive/src/msg.c b/clients/opal/udp/models/send_receive/src/msg.c index 00c519328..911133c72 100644 --- a/clients/opal/udp/models/send_receive/src/msg.c +++ b/clients/opal/udp/models/send_receive/src/msg.c @@ -7,18 +7,23 @@ *********************************************************************************/ #ifdef __linux__ - #include + #include #elif defined(__PPC__) /* Xilinx toolchain */ - #include - #define bswap_32(x) Xil_EndianSwap32(x) + #include + #define bswap_16(x) Xil_EndianSwap16(x) + #define bswap_32(x) Xil_EndianSwap32(x) #endif #include "msg.h" void msg_swap(struct msg *m) { - int i; - for (i = 0; i < m->length; i++) + m->length = bswap_16(m->length); + m->sequence = bswap_32(m->sequence); + m->ts.sec = bswap_32(m->ts.sec); + m->ts.nsec = bswap_32(m->ts.nsec); + + for (int i = 0; i < m->length; i++) m->data[i].i = bswap_32(m->data[i].i); m->endian ^= 1; diff --git a/clients/opal/udp/models/send_receive/src/s2ss.c b/clients/opal/udp/models/send_receive/src/s2ss.c index 9beef8095..4ac32161a 100644 --- a/clients/opal/udp/models/send_receive/src/s2ss.c +++ b/clients/opal/udp/models/send_receive/src/s2ss.c @@ -69,7 +69,7 @@ void Tick(int sig, siginfo_t *si, void *ptr) OpalGetAsyncModelTime(IconCtrlStruct, &CpuTime, &ModelTime); OpalPrint("%s: CpuTime: %llu\tModelTime: %.3f\tSequence: %hu\tValue: %.2f\n", - PROGNAME, (CpuTime - CpuTimeStart) / CPU_TICKS, ModelTime, ntohs(msg_send->sequence), msg_send->data[0].f); + PROGNAME, (CpuTime - CpuTimeStart) / CPU_TICKS, ModelTime, msg_send->sequence, msg_send->data[0].f); } #endif /* _DEBUG */ @@ -131,8 +131,8 @@ static void *SendToIPPort(void *arg) msg.data[i].f = (float) mdldata[i]; /* Convert to network byte order */ - msg.sequence = htonl(seq++); - msg.length = htons(msg.length); + msg.sequence = seq++; + msg.length = msg.length; /* Perform the actual write to the ip port */ if (SendPacket((char *) &msg, MSG_LEN(&msg)) < 0) @@ -206,18 +206,15 @@ static void *RecvFromIPPort(void *arg) OpalPrint("%s: Received no data. Skipping..\n", PROGNAME); continue; } - - /* Convert to host byte order */ - msg.sequence = ntohl(msg.sequence); - msg.length = ntohs(msg.length); + + /* Convert message to host endianess */ + if (msg.endian != MSG_ENDIAN_HOST) + msg_swap(&msg); if (n != MSG_LEN(&msg)) { OpalPrint("%s: Received incoherent packet (size: %d, complete: %d)\n", PROGNAME, n, MSG_LEN(&msg)); continue; } - - if (msg.endian != MSG_ENDIAN_HOST) - msg_swap(&msg); /* Update OPAL model */ OpalSetAsyncRecvIconStatus(msg.sequence, RecvID); /* Set the Status to the message ID */ diff --git a/server/include/msg.h b/server/include/msg.h index 0bb043c81..afce6df70 100644 --- a/server/include/msg.h +++ b/server/include/msg.h @@ -26,11 +26,11 @@ enum msg_flags { MSG_PRINT_ALL = 0xFF }; -/** Swap a message to host byte order. +/** Swaps message contents byte-order. * * Message can either be transmitted in little or big endian * format. The actual endianess for a message is defined by the - * msg::byteorder field. + * msg::endian field. This covers msg::length, msg::sequence, msg::data and msg::ts fields. * Received message are usally converted to the endianess of the host. * This is required for further sanity checks of the sequence number * or parsing of the data. diff --git a/server/include/msg_format.h b/server/include/msg_format.h index eb680a57e..dfda52201 100644 --- a/server/include/msg_format.h +++ b/server/include/msg_format.h @@ -81,18 +81,16 @@ struct msg #endif unsigned rsvd2 : 8; /**< Reserved bits */ - /** The number of values in msg::data[] */ - uint16_t length; - - uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages */ + uint16_t length; /**< The number of values in msg::data[]. Endianess is specified in msg::endian. */ + uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages. Endianess is specified in msg::endian. */ - /** A timestamp per message */ + /** A timestamp per message. Endianess is specified in msg::endian. */ struct { uint32_t sec; /**< Seconds since 1970-01-01 00:00:00 */ - uint32_t nsec; /**< Nanoseconds since 1970-01-01 00:00:00 */ + uint32_t nsec; /**< Nanoseconds of the current second. */ } ts; - /** The message payload */ + /** The message payload. Endianess is specified in msg::endian. */ union { float f; /**< Floating point values (note msg::endian) */ uint32_t i; /**< Integer values (note msg::endian) */ diff --git a/server/src/msg.c b/server/src/msg.c index 20cf4639e..cd9e7ea04 100644 --- a/server/src/msg.c +++ b/server/src/msg.c @@ -14,6 +14,7 @@ #include #elif defined(__PPC__) /* Xilinx toolchain */ #include + #define bswap_16(x) Xil_EndianSwap16(x) #define bswap_32(x) Xil_EndianSwap32(x) #endif @@ -23,8 +24,12 @@ void msg_swap(struct msg *m) { - int i; - for (i = 0; i < m->length; i++) + m->length = bswap_16(m->length); + m->sequence = bswap_32(m->sequence); + m->ts.sec = bswap_32(m->ts.sec); + m->ts.nsec = bswap_32(m->ts.nsec); + + for (int i = 0; i < m->length; i++) m->data[i].i = bswap_32(m->data[i].i); m->endian ^= 1; diff --git a/server/src/socket.c b/server/src/socket.c index 437e13f60..4629efa93 100644 --- a/server/src/socket.c +++ b/server/src/socket.c @@ -210,19 +210,15 @@ int socket_read(struct node *n, struct msg *pool, int poolsize, int first, int c for (int i = 0; i < cnt; i++) { struct msg *m = &pool[(first+poolsize+i) % poolsize]; - /* Convert headers to host byte order */ - m->sequence = ntohl(m->sequence); - m->length = ntohs(m->length); + /* Convert message to host endianess */ + if (m->endian != MSG_ENDIAN_HOST) + msg_swap(m); /* Check integrity of packet */ if (bytes / cnt != MSG_LEN(m)) error("Invalid message len: %u for node '%s'", MSG_LEN(m), n->name); bytes -= MSG_LEN(m); - - /* Convert message to host endianess */ - if (m->endian != MSG_ENDIAN_HOST) - msg_swap(m); } /* Check packet integrity */ @@ -246,9 +242,6 @@ int socket_write(struct node *n, struct msg *pool, int poolsize, int first, int if (m->type == MSG_TYPE_EMPTY) continue; - /* Convert headers to network byte order */ - n->sequence = htons(n->sequence); - iov[sent].iov_base = m; iov[sent].iov_len = MSG_LEN(m); From 7b941981583d90cc9f7c1169def1b2d254d43246 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 8 Oct 2015 10:49:51 +0200 Subject: [PATCH 16/81] Added last value to histogram --- server/include/hist.h | 2 ++ server/src/hist.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/server/include/hist.h b/server/include/hist.h index e1bbdfdcb..1f117b088 100644 --- a/server/include/hist.h +++ b/server/include/hist.h @@ -33,6 +33,8 @@ struct hist { double highest; /** The lowest value observed (may be lower than #low). */ double lowest; + /** The last value which has been put into the buckets */ + double last; /** The number of buckets in #data. */ int length; diff --git a/server/src/hist.c b/server/src/hist.c index 9d8cb9a76..ed8d36c39 100644 --- a/server/src/hist.c +++ b/server/src/hist.c @@ -38,6 +38,8 @@ void hist_destroy(struct hist *h) void hist_put(struct hist *h, double value) { int idx = INDEX(h, value); + + h->last = value; /* Update min/max */ if (value > h->highest) From 9439a88877ec227a4aaeb57dd122117d556d9233 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 12:50:35 +0200 Subject: [PATCH 17/81] replaced linked list by dynamic array (closes #6) --- server/include/list.h | 85 ++++++++++---------------- server/src/cfg.c | 8 +-- server/src/if.c | 14 ++--- server/src/list.c | 137 +++++++++++++++++------------------------- server/src/node.c | 6 +- server/src/path.c | 21 ++++--- server/src/server.c | 22 +++---- server/src/socket.c | 11 ++-- 8 files changed, 127 insertions(+), 177 deletions(-) diff --git a/server/include/list.h b/server/include/list.h index 335b26365..f1611cc00 100644 --- a/server/include/list.h +++ b/server/include/list.h @@ -14,83 +14,62 @@ #include -/* Forward declarations */ -struct list_elm; -struct node; -struct path; -struct interface; -struct socket; -struct gtfpga; -struct opal; +#define LIST_CHUNKSIZE 16 /** Static list initialization */ -#define LIST_INIT(dtor) { \ - .head = NULL, \ - .tail = NULL, \ - .length = 0, \ - .lock = PTHREAD_MUTEX_INITIALIZER, \ - .destructor = dtor \ +#define LIST_INIT(dtor) { \ + .start = NULL, \ + .end = NULL, \ + .length = 0, \ + .capacity = 0, \ + .lock = PTHREAD_MUTEX_INITIALIZER, \ + .destructor = dtor \ } - -#define FOREACH(list, elm) \ - for ( struct list_elm *elm = (list)->head; \ - elm; elm = elm->next ) - -#define FOREACH_R(list, elm) \ - for ( struct list_elm *elm = (list)->tail; \ - elm; elm = elm->prev ) -#define list_first(list) ((list)->head) -#define list_last(list) ((list)->head) #define list_length(list) ((list)->length) +#define list_at(list, index) ((list)->length > index ? (list)->start[index] : NULL) + +#define list_first(list) list_at(list, 0) +#define list_last(list) list_at(list, (list)->length-1) +#define list_foreach(ptr, list) for (int _i = 0, _p; _p = 1, _i < (list)->length; _i++) \ + for (ptr = (list)->start[_i]; _p--; ) /** Callback to destroy list elements. * * @param data A pointer to the data which should be freed. */ -typedef void (*dtor_cb_t)(void *data); - -typedef int (*cmp_cb_t)(void *, void *); +typedef void (*dtor_cb_t)(void *); + +/** Callback to search or sort a list. */ +typedef int (*cmp_cb_t)(const void *, const void *); struct list { - struct list_elm *head, *tail; - int length; - - dtor_cb_t destructor; - pthread_mutex_t lock; -}; - -struct list_elm { - union { - void *ptr; - struct node *node; - struct node_type *type; - struct path *path; - struct interface *interface; - struct socket *socket; - struct opal *opal; - struct gtfpga *gtfpga; - struct hook *hook; - } /* anonymous */; - - int priority; - struct list_elm *prev, *next; + void **start; /**< Array of pointers to list elements */ + void **end; /**< Array of pointers to list elements */ + size_t capacity; /**< Size of list::start in elements */ + size_t length; /**< Number of elements of list::start which are in use */ + dtor_cb_t destructor; /**< A destructor which gets called for every list elements during list_destroy() */ + pthread_mutex_t lock; /**< A mutex to allow thread-safe accesses */ }; +/** Initialize a list */ void list_init(struct list *l, dtor_cb_t dtor); +/** Destroy a list and call destructors for all list elements */ void list_destroy(struct list *l); +/** Append an element to the end of the list */ void list_push(struct list *l, void *p); -void list_insert(struct list *l, int prio, void *p); - /** Search the list for an element whose first element is a character array which matches name. * * @see Only possible because of ยง1424 of http://c0x.coding-guidelines.com/6.7.2.1.html */ -void * list_lookup(const struct list *l, const char *name); +void * list_lookup(struct list *l, const char *name); -void * list_search(const struct list *l, cmp_cb_t cmp, void *ctx); +void * list_search(struct list *l, cmp_cb_t cmp, void *ctx); + +/** Sort the list using the quicksort algorithm of libc */ +void list_sort(struct list *l, cmp_cb_t cmp); #endif /* _LIST_H_ */ diff --git a/server/src/cfg.c b/server/src/cfg.c index 18db5fa85..6d547379c 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -117,7 +117,7 @@ int config_parse_path(config_setting_t *cfg, config_parse_nodelist(cfg_out, &p->destinations, nodes); if (list_length(&p->destinations) >= 1) - p->out = list_first(&p->destinations)->node; + p->out = (struct node *) list_first(&p->destinations); else cerror(cfg, "Missing output node for path"); @@ -141,9 +141,9 @@ int config_parse_path(config_setting_t *cfg, p->in->refcnt++; p->in->vt->refcnt++; - FOREACH(&p->destinations, it) { - it->node->refcnt++; - it->node->vt->refcnt++; + list_foreach(struct node *node, &p->destinations) { + node->refcnt++; + node->vt->refcnt++; } if (reverse) { diff --git a/server/src/if.c b/server/src/if.c index 18e839d92..2b4783b74 100644 --- a/server/src/if.c +++ b/server/src/if.c @@ -58,7 +58,7 @@ void if_destroy(struct interface *i) int if_start(struct interface *i, int affinity) { - info("Starting interface '%s' which is used by %u sockets", rtnl_link_get_name(i->nl_link), list_length(&i->sockets)); + info("Starting interface '%s' which is used by %zu sockets", rtnl_link_get_name(i->nl_link), list_length(&i->sockets)); { INDENT /* Set affinity for network interfaces (skip _loopback_ dev) */ @@ -66,8 +66,7 @@ int if_start(struct interface *i, int affinity) /* Assign fwmark's to socket nodes which have netem options */ int ret, mark = 0; - FOREACH(&i->sockets, it) { - struct socket *s = it->socket; + list_foreach(struct socket *s, &i->sockets) { if (s->tc_qdisc) s->mark = 1 + mark++; } @@ -90,8 +89,7 @@ int if_start(struct interface *i, int affinity) error("Failed to setup priority queuing discipline: %s", nl_geterror(ret)); /* Create netem qdisks and appropriate filter per netem node */ - FOREACH(&i->sockets, it) { - struct socket *s = it->socket; + list_foreach(struct socket *s, &i->sockets) { if (s->tc_qdisc) { ret = tc_mark(i, &s->tc_classifier, TC_HANDLE(1, s->mark), s->mark); if (ret) @@ -210,9 +208,9 @@ int if_set_affinity(struct interface *i, int affinity) struct interface * if_lookup_index(int index) { - FOREACH(&interfaces, it) { - if (rtnl_link_get_ifindex(it->interface->nl_link) == index) - return it->interface; + list_foreach(struct interface *i, &interfaces) { + if (rtnl_link_get_ifindex(i->nl_link) == index) + return i; } return NULL; diff --git a/server/src/list.c b/server/src/list.c index 5ef309b33..4bbdb89f4 100644 --- a/server/src/list.c +++ b/server/src/list.c @@ -8,9 +8,9 @@ * Unauthorized copying of this file, via any medium is strictly prohibited. *********************************************************************************/ +#include #include -#include "utils.h" #include "list.h" void list_init(struct list *l, dtor_cb_t dtor) @@ -19,24 +19,28 @@ void list_init(struct list *l, dtor_cb_t dtor) l->destructor = dtor; l->length = 0; - l->head = NULL; - l->tail = NULL; + l->capacity = 0; + + l->start = NULL; + l->end = NULL; } void list_destroy(struct list *l) { pthread_mutex_lock(&l->lock); - struct list_elm *elm = l->head; - while (elm) { - struct list_elm *tmp = elm; - elm = elm->next; - - if (l->destructor) - l->destructor(tmp->ptr); - - free(tmp); + if (l->destructor) { + for (void *e = l->start; e != l->end; e++) + l->destructor(e); } + + free(l->start); + + l->start = + l->end = NULL; + + l->length = + l->capacity = 0; pthread_mutex_unlock(&l->lock); pthread_mutex_destroy(&l->lock); @@ -44,83 +48,54 @@ void list_destroy(struct list *l) void list_push(struct list *l, void *p) { - struct list_elm *e = alloc(sizeof(struct list_elm)); - pthread_mutex_lock(&l->lock); - - e->ptr = p; - e->prev = l->tail; - e->next = NULL; - - if (l->tail) - l->tail->next = e; - if (l->head) - l->head->prev = e; - else - l->head = e; - - l->tail = e; + + /* Resize array if out of capacity */ + if (l->end == l->start + l->capacity) { + l->capacity += LIST_CHUNKSIZE; + l->start = realloc(l->start, l->capacity * sizeof(void *)); + } + + l->start[l->length] = p; l->length++; + l->end = &l->start[l->length]; pthread_mutex_unlock(&l->lock); } -void list_insert(struct list *l, int prio, void *p) +void * list_lookup(struct list *l, const char *name) { - struct list_elm *d; - struct list_elm *e = alloc(sizeof(struct list_elm)); + int cmp(const void *a, const void *b) { + return strcmp(*(char **) a, b); + } - e->priority = prio; - e->ptr = p; + return list_search(l, cmp, (void *) name); +} + +void * list_search(struct list *l, cmp_cb_t cmp, void *ctx) +{ + pthread_mutex_lock(&l->lock); + + void *e; + list_foreach(e, l) { + if (!cmp(e, ctx)) + goto out; + } + + e = NULL; /* not found */ + +out: pthread_mutex_unlock(&l->lock); + + return e; +} + +void list_sort(struct list *l, cmp_cb_t cmp) +{ + int cmp_helper(const void *a, const void *b) { + return cmp(*(void **) a, *(void **) b); + } pthread_mutex_lock(&l->lock); - - /* Search for first entry with higher priority */ - for (d = l->head; d && d->priority < prio; d = d->next); - - /* Insert new element in front of d */ - e->next = d; - - if (d) { /* Between or Head */ - e->prev = d->prev; - - if (d == l->head) /* Head */ - l->head = e; - else /* Between */ - d->prev = e; - } - else { /* Tail or Head */ - e->prev = l->tail; - - if (l->length == 0) /* List was empty */ - l->head = e; - else - l->tail->next = e; - - l->tail = e; - } - - l->length++; - + qsort(l->start, l->length, sizeof(void *), cmp_helper); pthread_mutex_unlock(&l->lock); -} - -void * list_lookup(const struct list *l, const char *name) -{ - FOREACH(l, it) { - if (!strcmp(*(char **) it->ptr, name)) - return it->ptr; - } - - return NULL; -} - -void * list_search(const struct list *l, cmp_cb_t cmp, void *ctx) -{ - FOREACH(l, it) { - if (!cmp(it->ptr, ctx)) - return it->ptr; - } - - return NULL; -} +} \ No newline at end of file diff --git a/server/src/node.c b/server/src/node.c index 034ac2d61..352fa3814 100644 --- a/server/src/node.c +++ b/server/src/node.c @@ -35,8 +35,7 @@ struct list node_types = LIST_INIT(NULL); int node_init(int argc, char *argv[], struct settings *set) { INDENT - FOREACH(&node_types, it) { - const struct node_type *vt = it->type; + list_foreach(const struct node_type *vt, &node_types) { if (vt->refcnt) { info("Initializing '%s' node type", vt->name); vt->init(argc, argv, set); @@ -49,8 +48,7 @@ int node_init(int argc, char *argv[], struct settings *set) int node_deinit() { INDENT /* De-initialize node types */ - FOREACH(&node_types, it) { - struct node_type *vt = it->type; + list_foreach(const struct node_type *vt, &node_types) { if (vt->refcnt) { info("De-initializing '%s' node type", vt->name); vt->deinit(); diff --git a/server/src/path.c b/server/src/path.c index 858dbbfe9..1bb5e3ed2 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -23,16 +23,16 @@ extern struct settings settings; static void path_write(struct path *p) { - FOREACH(&p->destinations, it) { + list_foreach(struct node *n, &p->destinations) { int sent = node_write( - it->node, /* Destination node */ - p->pool, /* Pool of received messages */ - p->poolsize, /* Size of the pool */ - p->received - it->node->combine,/* Index of the first message which should be sent */ - it->node->combine /* Number of messages which should be sent */ + n, /* Destination node */ + p->pool, /* Pool of received messages */ + p->poolsize, /* Size of the pool */ + p->received - n->combine,/* Index of the first message which should be sent */ + n->combine /* Number of messages which should be sent */ ); - debug(1, "Sent %u messages to node '%s'", sent, it->node->name); + debug(1, "Sent %u messages to node '%s'", sent, n->name); p->sent += sent; clock_gettime(CLOCK_REALTIME, &p->ts_sent); @@ -42,8 +42,9 @@ static void path_write(struct path *p) int path_run_hook(struct path *p, enum hook_type t) { int ret = 0; - FOREACH(&p->hooks[t], it) ret += ((hook_cb_t) it->ptr)(p); + list_foreach(struct hook *h, &p->hooks) { + } return ret; } @@ -177,8 +178,8 @@ char * path_print(struct path *p) if (list_length(&p->destinations) > 1) { strcatf(&buf, " ["); - FOREACH(&p->destinations, it) - strcatf(&buf, " %s", it->node->name); + list_foreach(struct node *n, &p->destinations) + strcatf(&buf, " %s", n->name); strcatf(&buf, " ]"); } else diff --git a/server/src/server.c b/server/src/server.c index 5c5f29e7f..ba21a6443 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -38,12 +38,12 @@ static config_t config; static void quit() { info("Stopping paths"); - FOREACH(&paths, it) - path_stop(it->path); + list_foreach(struct path *p, &paths) + path_stop(p); info("Stopping nodes"); - FOREACH(&nodes, it) - node_stop(it->node); + list_foreach(struct node *n, &nodes) + node_stop(n); info("De-initializing node types"); node_deinit(); @@ -185,21 +185,21 @@ int main(int argc, char *argv[]) node_init(argc, argv, &settings); info("Starting nodes"); - FOREACH(&nodes, it) - node_start(it->node); + list_foreach(struct node *n, &nodes) + node_start(n); info("Starting paths"); - FOREACH(&paths, it) - path_start(it->path); + list_foreach(struct path *p, &paths) + path_start(p); /* Run! */ if (settings.stats > 0) { stats_header(); - for (;;) FOREACH(&paths, it) { + do list_foreach(struct path *p, &paths) { usleep(settings.stats * 1e6); - path_run_hook(it->path, HOOK_PERIODIC); - } + path_run_hook(p, HOOK_PERIODIC); + } while (1); } else pause(); diff --git a/server/src/socket.c b/server/src/socket.c index 4629efa93..3a7ad6a8c 100644 --- a/server/src/socket.c +++ b/server/src/socket.c @@ -47,8 +47,7 @@ int socket_init(int argc, char * argv[], struct settings *set) list_init(&interfaces, (dtor_cb_t) if_destroy); /* Gather list of used network interfaces */ - FOREACH(&sockets, it) { - struct socket *s = it->socket; + list_foreach(struct socket *s, &sockets) { struct rtnl_link *link; /* Determine outgoing interface */ @@ -67,16 +66,16 @@ int socket_init(int argc, char * argv[], struct settings *set) } /** @todo Improve mapping of NIC IRQs per path */ - FOREACH(&interfaces, it) - if_start(it->interface, set->affinity); + list_foreach(struct interface *i, &interfaces) + if_start(i, set->affinity); return 0; } int socket_deinit() { INDENT - FOREACH(&interfaces, it) - if_stop(it->interface); + list_foreach(struct interface *i, &interfaces) + if_stop(i); list_destroy(&interfaces); From d6cc209a79a631e7257baf7477ef43480c8fb2cb Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 13:04:52 +0200 Subject: [PATCH 18/81] smaller cleanups --- server/include/file.h | 2 +- server/include/msg_format.h | 3 ++- server/src/hist.c | 2 +- server/src/node.c | 1 + server/src/path.c | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/include/file.h b/server/include/file.h index c6bfd458b..51733cc32 100644 --- a/server/include/file.h +++ b/server/include/file.h @@ -42,7 +42,7 @@ struct file { struct timespec offset; /**< An offset between the timestamp in the input file and the current time */ double rate; /**< The sending rate. */ - int tfd; /**< Timer file descriptor. Blocks until 1/rate seconds are elapsed. */ + int tfd; /**< Timer file descriptor. Blocks until 1 / rate seconds are elapsed. */ }; /** @see node_vtable::init */ diff --git a/server/include/msg_format.h b/server/include/msg_format.h index dfda52201..d919c26bc 100644 --- a/server/include/msg_format.h +++ b/server/include/msg_format.h @@ -44,9 +44,10 @@ #error "Unknown byte order!" #endif -/** The total length of a message */ +/** The total size in bytes of a message */ #define MSG_LEN(msg) (4 * ((msg)->length + 4)) +/** The timestamp of a message in struct timespec format */ #define MSG_TS(msg) (struct timespec) { \ .tv_sec = (msg)->ts.sec, \ .tv_nsec = (msg)->ts.nsec \ diff --git a/server/src/hist.c b/server/src/hist.c index ed8d36c39..b92492fb4 100644 --- a/server/src/hist.c +++ b/server/src/hist.c @@ -118,7 +118,7 @@ void hist_print(struct hist *h) hist_plot(h); char *buf = hist_dump(h); - info(buf); + info("Matlab: %s", buf); free(buf); } } diff --git a/server/src/node.c b/server/src/node.c index 352fa3814..71b3dba29 100644 --- a/server/src/node.c +++ b/server/src/node.c @@ -54,6 +54,7 @@ int node_deinit() vt->deinit(); } } + return 0; } diff --git a/server/src/path.c b/server/src/path.c index 1bb5e3ed2..d15393682 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -94,7 +94,7 @@ static void * path_run(void *arg) /* For each received message... */ for (int i = 0; i < recv; i++) { p->previous = p->current; - p->current = &p->pool[ p->received % p->poolsize]; + p->current = &p->pool[p->received % p->poolsize]; p->received++; From 9b5cf26bac0665cb918dbe02df8eb081382141f1 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 13:06:59 +0200 Subject: [PATCH 19/81] added new 'msgsize' option to path (not yet used) --- server/include/path.h | 2 ++ server/src/cfg.c | 2 ++ server/src/path.c | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/server/include/path.h b/server/include/path.h index 519832040..6838fa7ad 100644 --- a/server/include/path.h +++ b/server/include/path.h @@ -47,6 +47,8 @@ struct path /** Send messages with a fixed rate over this path */ double rate; + /** Maximum number of values per message for this path */ + int msgsize; /** Size of the history buffer in number of messages */ int poolsize; /** A circular buffer of past messages */ diff --git a/server/src/cfg.c b/server/src/cfg.c index 6d547379c..a0a59bfd1 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -134,6 +134,8 @@ int config_parse_path(config_setting_t *cfg, p->rate = 0; /* disabled */ if (!config_setting_lookup_int(cfg, "poolsize", &p->poolsize)) p->poolsize = DEFAULT_POOLSIZE; + if (!config_setting_lookup_int(cfg, "msgsize", &p->msgsize)) + p->msgsize = MAX_VALUES; p->cfg = cfg; diff --git a/server/src/path.c b/server/src/path.c index d15393682..85a7f80a7 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -122,7 +122,7 @@ static void * path_run(void *arg) int path_start(struct path *p) { INDENT char *buf = path_print(p); - info("Starting path: %s (poolsize = %u)", buf, p->poolsize); + info("Starting path: %s (poolsize = %u, msgsize = %u, #hooks = %zu)", buf, p->poolsize, p->msgsize, list_length(&p->hooks)); free(buf); if (path_run_hook(p, HOOK_PATH_START)) From c9a3461ffebf13b5692b8611b182d6e63bb1fe75 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 13:08:39 +0200 Subject: [PATCH 20/81] improved epoch related settings for file node --- documentation/Mainpage.md | 2 + documentation/clients/File.md | 28 ++++-- server/etc/example.conf | 5 +- server/include/file.h | 6 +- server/src/file.c | 173 ++++++++++++++++++++++------------ 5 files changed, 142 insertions(+), 72 deletions(-) diff --git a/documentation/Mainpage.md b/documentation/Mainpage.md index 35525edca..455c0e5b2 100644 --- a/documentation/Mainpage.md +++ b/documentation/Mainpage.md @@ -13,6 +13,8 @@ S2SS is used in distributed- and co-simulation scenarios and developed for the f The project consists of a server daemon and several client modules which are documented here. +[TOC] + ### Server The server simply acts as a gateway to forward simulation data from one client to another. diff --git a/documentation/clients/File.md b/documentation/clients/File.md index 28a14b112..75428cd18 100644 --- a/documentation/clients/File.md +++ b/documentation/clients/File.md @@ -31,16 +31,31 @@ Specifies the mode which should be used to open the output file. See [open(2)](http://man7.org/linux/man-pages/man2/open.2.html) for an explanation of allowed values. The default value is `w+` which will start writing at the beginning of the file and create it in case it does not exist yet. -#### `epoch_mode` *("now"|"relative"|"absolute")* +#### `epoch_mode` *("direct"|"wait" | "relative"|"absolute")* +The *epoch* describes the point in time when the first message will be read from the file. This setting allows to select the behaviour of the following `epoch` setting. It can be used to adjust the point in time when the first value should be read. The behaviour of `epoch` is depending on the value of `epoch_mode`. - - `epoch_mode = now`: The first value is read at *now* + `epoch` seconds. - - `epoch_mode = relative`: The first value is read at *start* + `epoch` seconds. - - `epoch_mode = absolute`: The first value is read at `epoch` seconds after 1970-01-01 00:00:00. +To facilitate the following description of supported `epoch_mode`'s, we will introduce some intermediate variables (timestamps). +Those variables will also been displayed during the startup phase of the server to simplify debugging. + +- `epoch` is the value of the `epoch` setting. +- `first` is the timestamp of the first message / line in the input file. +- `offset` will be added to the timestamps in the file to obtain the real time when the message will be sent. +- `start` is the point in time when the first message will be sent (`first + offset`). +- `eta` the time to wait until the first message will be send (`start - now`) + +The supported values for `epoch_mode`: + + | `epoch_mode` | `offset` | `start = first + offset` | + | -----------: | :-------------------: | :----------------------: | + | `direct` | `now - first + epoch` | `now + epoch` | + | `wait` | `now + epoch` | `now + first` | + | `relative` | `epoch` | `first + epoch` | + | `absolute` | `epoch - first` | `epoch` | #### `send_rate` *(float)* @@ -60,8 +75,9 @@ If this setting has a non-zero value, the default behaviour is overwritten with in = "logs/file_input.log", # These options specify the path prefix where the the files are stored out = "logs/file_output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3)) - epoch_mode = "now" # One of: - # now (default) + epoch_mode = "direct" # One of: + # direct (default) + # wait # relative # absolute diff --git a/server/etc/example.conf b/server/etc/example.conf index 22ce22dc1..d6e74e6d4 100644 --- a/server/etc/example.conf +++ b/server/etc/example.conf @@ -90,8 +90,9 @@ nodes = { in = "logs/file_input.log", # These options specify the path prefix where the the files are stored out = "logs/file_output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3)) - epoch_mode = "now" # One of: - # now (default) + epoch_mode = "direct" # One of: + # direct (default) + # wait # relative # absolute diff --git a/server/include/file.h b/server/include/file.h index 51733cc32..ca633b749 100644 --- a/server/include/file.h +++ b/server/include/file.h @@ -32,17 +32,19 @@ struct file { const char *file_mode; /**< The mode for fopen() which is used for the out file. */ enum epoch_mode { - EPOCH_NOW, + EPOCH_DIRECT, + EPOCH_WAIT, EPOCH_RELATIVE, EPOCH_ABSOLUTE } epoch_mode; /**< Specifies how file::offset is calculated. */ - struct timespec start; /**< The first timestamp of the input file. */ + struct timespec first; /**< The first timestamp in the file file::path_in */ struct timespec epoch; /**< The epoch timestamp from the configuration. */ struct timespec offset; /**< An offset between the timestamp in the input file and the current time */ double rate; /**< The sending rate. */ int tfd; /**< Timer file descriptor. Blocks until 1 / rate seconds are elapsed. */ + int sequence; /**< Last sequence of this node */ }; /** @see node_vtable::init */ diff --git a/server/src/file.c b/server/src/file.c index bb06e2058..ab60bacf1 100644 --- a/server/src/file.c +++ b/server/src/file.c @@ -30,9 +30,55 @@ char * file_print(struct node *n) { struct file *f = n->file; char *buf = NULL; + + if (f->path_in) { + const char *epoch_mode_str = NULL; + switch (f->epoch_mode) { + case EPOCH_DIRECT: epoch_mode_str = "direct"; break; + case EPOCH_WAIT: epoch_mode_str = "wait"; break; + case EPOCH_RELATIVE: epoch_mode_str = "relative"; break; + case EPOCH_ABSOLUTE: epoch_mode_str = "absolute"; break; + } + + strcatf(&buf, "in=%s, epoch_mode=%s, epoch=%.2f, ", + f->path_in, + epoch_mode_str, + time_to_double(&f->epoch) + ); + + if (f->rate) + strcatf(&buf, "rate=%.1f, ", f->rate); + } + + if (f->path_out) { + strcatf(&buf, "out=%s, mode=%s, ", + f->path_out, + f->file_mode + ); + } + + if (f->first.tv_sec || f->first.tv_nsec) + strcatf(&buf, "first=%.2f, ", time_to_double(&f->first)); + + if (f->offset.tv_sec || f->offset.tv_nsec) + strcatf(&buf, "offset=%.2f, ", time_to_double(&f->offset)); + + if ((f->first.tv_sec || f->first.tv_nsec) && + (f->offset.tv_sec || f->offset.tv_nsec)) { + struct timespec eta, now; + clock_gettime(CLOCK_REALTIME, &now); - return strcatf(&buf, "in=%s, out=%s, mode=%s, rate=%.1f, epoch_mode=%u, epoch=%.0f", - f->path_in, f->path_out, f->file_mode, f->rate, f->epoch_mode, time_to_double(&f->epoch)); + eta = time_add(&f->first, &f->offset); + eta = time_diff(&now, &eta); + + if (eta.tv_sec || eta.tv_nsec) + strcatf(&buf, "eta=%.2f sec, ", time_to_double(&eta)); + } + + if (strlen(buf) > 2) + buf[strlen(buf) - 2] = 0; + + return buf; } int file_parse(config_setting_t *cfg, struct node *n) @@ -65,10 +111,12 @@ int file_parse(config_setting_t *cfg, struct node *n) f->epoch = time_from_double(epoch_flt); if (!config_setting_lookup_string(n->cfg, "epoch_mode", &epoch_mode)) - epoch_mode = "now"; + epoch_mode = "direct"; - if (!strcmp(epoch_mode, "now")) - f->epoch_mode = EPOCH_NOW; + if (!strcmp(epoch_mode, "direct")) + f->epoch_mode = EPOCH_DIRECT; + else if (!strcmp(epoch_mode, "wait")) + f->epoch_mode = EPOCH_WAIT; else if (!strcmp(epoch_mode, "relative")) f->epoch_mode = EPOCH_RELATIVE; else if (!strcmp(epoch_mode, "absolute")) @@ -86,17 +134,51 @@ int file_open(struct node *n) struct file *f = n->file; if (f->path_in) { - f->in = fopen(f->path_in, "r"); + /* Open file */ + f->in = fopen(f->path_in, "r"); if (!f->in) serror("Failed to open file for reading: '%s'", f->path_in); + /* Create timer */ f->tfd = timerfd_create(CLOCK_REALTIME, 0); if (f->tfd < 0) serror("Failed to create timer"); + + /* Get current time */ + struct timespec now, eta; + clock_gettime(CLOCK_REALTIME, &now); - /* Arm the timer */ + /* Get timestamp of first line */ + struct msg m; + int ret = msg_fscan(f->in, &m, NULL, NULL); rewind(f->in); + if (ret < 0) + error("Failed to read first timestamp of node '%s'", n->name); + + f->first = MSG_TS(&m); + + /* Set offset depending on epoch_mode */ + switch (f->epoch_mode) { + case EPOCH_DIRECT: /* read first value at now + epoch */ + f->offset = time_diff(&f->first, &now); + f->offset = time_add(&f->offset, &f->epoch); + break; + + case EPOCH_WAIT: /* read first value at now + first + epoch */ + f->offset = now; + f->offset = time_add(&f->offset, &f->epoch); + break; + + case EPOCH_RELATIVE: /* read first value at first + epoch */ + f->offset = f->epoch; + break; + + case EPOCH_ABSOLUTE: /* read first value at f->epoch */ + f->offset = time_diff(&f->first, &f->epoch); + break; + } + + /* Arm the timer with a fixed rate */ if (f->rate) { - /* Send with fixed rate */ struct itimerspec its = { .it_interval = time_from_double(1 / f->rate), .it_value = { 1, 0 }, @@ -106,43 +188,14 @@ int file_open(struct node *n) if (ret) serror("Failed to start timer"); } - else { - struct msg m; - /* Get current time */ - struct timespec now, first, eta; - clock_gettime(CLOCK_REALTIME, &now); - - /* Get timestamp of first sample */ - msg_fscan(f->in, &m, NULL, NULL); - first = MSG_TS(&m); - - /* Set offset depending on epoch_mode */ - switch (f->epoch_mode) { - case EPOCH_NOW: /* read first value at f->now + f->epoch */ - first = time_diff(&f->start, &now); - f->offset = time_add(&first, &f->epoch); - break; - case EPOCH_RELATIVE: /* read first value at f->start + f->epoch */ - f->offset = f->epoch; - break; - case EPOCH_ABSOLUTE: /* read first value at f->epoch */ - f->offset = time_diff(&f->start, &f->epoch); - break; - } - - eta = time_add(&f->start, &f->offset); - eta = time_diff(&now, &eta); - - debug(5, "Opened file '%s' as input for node '%s': start=%.2f, offset=%.2f, eta=%.2f sec", - f->path_in, n->name, - time_to_double(&f->start), - time_to_double(&f->offset), - time_to_double(&eta) - ); - } + + char *buf = file_print(n); + debug(4, "Opened file node '%s': %s", n->name, buf); + free(buf); } if (f->path_out) { + /* Open output file */ f->out = fopen(f->path_out, f->file_mode); if (!f->out) serror("Failed to open file for writing: '%s'", f->path_out); @@ -167,39 +220,35 @@ int file_close(struct node *n) int file_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt) { - int i = 0; + int values, flags, i = 0; struct file *f = n->file; if (f->in) { for (i = 0; i < cnt; i++) { struct msg *cur = &pool[(first+i) % poolsize]; - if (f->rate) { - /* Wait until epoch for the first time only */ - if (ftell(f->in) == 0) { - struct timespec until = time_add(&f->start, &f->offset); - if (timerfd_wait_until(f->tfd, &until)) - serror("Failed to wait for timer"); - } - /* Wait with fixed rate delay */ - else { - if (timerfd_wait(f->tfd) < 0) - serror("Failed to wait for timer"); - } + /* Get message and timestamp */ + values = msg_fscan(f->in, cur, &flags, NULL); + if (values < 0) { + if (!feof(f->in)) + warn("Failed to parse file of node '%s", n->name); - msg_fscan(f->in, cur, NULL, NULL); + return 0; } - else { - struct timespec until; - /* Get message and timestamp */ - msg_fscan(f->in, cur, NULL, NULL); + /* Fix missing sequence no */ + cur->sequence = f->sequence = (flags & MSG_PRINT_SEQUENCE) ? cur->sequence : f->sequence + 1; + + if (!f->rate || ftell(f->in) == 0) { + struct timespec until = time_add(&MSG_TS(cur), &f->offset); - /* Wait for next message / sampe */ - until = time_add(&MSG_TS(cur), &f->offset); if (timerfd_wait_until(f->tfd, &until) < 0) serror("Failed to wait for timer"); } + else { /* Wait with fixed rate delay */ + if (timerfd_wait(f->tfd) < 0) + serror("Failed to wait for timer"); + } } } else From a66331ccbe7af466d9f2ec14baee8f92dab46697 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 13:25:43 +0200 Subject: [PATCH 21/81] updated version number and maximum number of values --- server/include/config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/include/config.h b/server/include/config.h index 36fe004a4..1e10130b1 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -18,10 +18,10 @@ #endif /** The version number of the s2ss server */ -#define VERSION "v0.4-" _GIT_REV +#define VERSION "v0.5-" _GIT_REV /** Maximum number of float values in a message */ -#define MAX_VALUES 16 +#define MAX_VALUES 64 /** Maximum number of messages in the circular history buffer */ #define DEFAULT_POOLSIZE 32 From 9ceb05d823200bb6a496316cc4d1ac64d42de2db Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 13:30:41 +0200 Subject: [PATCH 22/81] rework of hook system. added new hooks: - skip_first --- server/Makefile | 2 +- server/include/cfg.h | 9 + server/include/config.h | 15 +- server/include/hooks.h | 96 ++++++---- server/include/path.h | 11 +- server/include/stats.h | 36 ---- server/src/cfg.c | 60 +++--- server/src/hooks.c | 411 +++++++++++++++++++++++++++++++--------- server/src/path.c | 54 ++---- server/src/server.c | 5 +- server/src/stats.c | 80 -------- 11 files changed, 453 insertions(+), 326 deletions(-) delete mode 100644 server/include/stats.h delete mode 100644 server/src/stats.c diff --git a/server/Makefile b/server/Makefile index 88b65f1d0..dbe1007fe 100644 --- a/server/Makefile +++ b/server/Makefile @@ -1,7 +1,7 @@ TARGETS = server send random receive test # Common objs -OBJS = path.o node.o hooks.o msg.o cfg.o stats.o +OBJS = path.o node.o hooks.o msg.o cfg.o # Helper libs OBJS += utils.o list.o hist.o log.o timing.o checks.o diff --git a/server/include/cfg.h b/server/include/cfg.h index 0cbb5e85d..994503133 100644 --- a/server/include/cfg.h +++ b/server/include/cfg.h @@ -82,6 +82,15 @@ int config_parse_nodelist(config_setting_t *cfg, struct list *nodes, struct list **/ int config_parse_hooklist(config_setting_t *cfg, struct list *hooks); +/** Parse a single hook and append it to the list. + * A hook definition is composed of the hook name and optional parameters + * seperated by a colon. + * + * Examples: + * "print:stdout" + */ +int config_parse_hook(config_setting_t *cfg, struct list *list); + /** Parse a single node and add it to the global configuration. * * @param cfg A libconfig object pointing to the node. diff --git a/server/include/config.h b/server/include/config.h index 1e10130b1..0afaa36ee 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -51,19 +51,18 @@ { "/sys/class/net/eth0/address" , "50:e5:49:eb:74:0c" }, \ { "/etc/machine-id", "0d8399d0216314f083b9ed2053a354a8" }, \ { "/dev/sda2", "\x53\xf6\xb5\xeb\x8b\x16\x46\xdc\x8d\x8f\x5b\x70\xb8\xc9\x1a\x2a", 0x468 } } - -/* Hard coded configuration of hook functions */ -#define HOOK_FIR_INDEX 0 /**< Which value inside a message should be filtered? */ + +/** Coefficients for simple FIR-LowPass: + * F_s = 1kHz, F_pass = 100 Hz, F_block = 300 + * + * Tip: Use MATLAB's filter design tool and export coefficients + * with the integrated C-Header export + */ #define HOOK_FIR_COEFFS { -0.003658148158728, -0.008882653268281, 0.008001024183003, \ 0.08090485991761, 0.2035239551043, 0.3040703593515, \ 0.3040703593515, 0.2035239551043, 0.08090485991761, \ 0.008001024183003, -0.008882653268281,-0.003658148158728 } -#define HOOK_TS_INDEX -1 /**< The last value of message should be overwritten by a timestamp. */ -#define HOOK_DECIMATE_RATIO 30 /**< Only forward every 30th message to the destination nodes. */ - -#define HOOK_DEDUP_TYPE HOOK_ASYNC -#define HOOK_DEDUP_TRESH 1e-3 /**< Do not send messages when difference of values to last message is smaller than this threshold */ /** Global configuration */ struct settings { /** Process priority (lower is better) */ diff --git a/server/include/hooks.h b/server/include/hooks.h index b54e11f82..17e3c503e 100644 --- a/server/include/hooks.h +++ b/server/include/hooks.h @@ -23,16 +23,17 @@ #include -#define REGISTER_HOOK(name, prio, fnc, type) \ -__attribute__((constructor)) void __register_ ## fnc () { \ - static struct hook h = { name, prio, fnc, type }; \ - list_push(&hooks, &h); \ +#define REGISTER_HOOK(name, prio, fnc, type) \ +__attribute__((constructor)) void __register_ ## fnc () { \ + static struct hook h = { name, NULL, prio, type, NULL, fnc }; \ + list_push(&hooks, &h); \ } /* The configuration of hook parameters is done in "config.h" */ /* Forward declarations */ struct path; +struct hook; /** This is a list of hooks which can be used in the configuration file. */ extern struct list hooks; @@ -40,34 +41,51 @@ extern struct list hooks; /** Callback type of hook function * * @param p The path which is processing this message. + * @param h The hook datastructure which contains parameter, name and private context for the hook. + * @param when Provides the type of hook for which this occurence of the callback function was executed. See hook_type for possible values. * @retval 0 Success. Continue processing and forwarding the message. * @retval <0 Error. Drop the message. */ -typedef int (*hook_cb_t)(struct path *p); +typedef int (*hook_cb_t)(struct path *p, struct hook *h, int when); -/** The type of a hook defines when a hook will be exectuted. */ +/** The type of a hook defines when a hook will be exectuted. This is used as a bitmask. */ enum hook_type { - HOOK_PATH_START, /**< Called whenever a path is started; before threads are created. */ - HOOK_PATH_STOP, /**< Called whenever a path is stopped; after threads are destoyed. */ - HOOK_PATH_RESTART, /**< Called whenever a new simulation case is started. This is detected by a sequence no equal to zero. */ + HOOK_PATH_START = 1 << 0, /**< Called whenever a path is started; before threads are created. */ + HOOK_PATH_STOP = 1 << 1, /**< Called whenever a path is stopped; after threads are destoyed. */ + HOOK_PATH_RESTART = 1 << 2, /**< Called whenever a new simulation case is started. This is detected by a sequence no equal to zero. */ - HOOK_PRE, /**< Called when a new packet of messages (samples) was received. */ - HOOK_POST, /**< Called after each message (sample) of a packet was processed. */ - HOOK_MSG, /**< Called for each message (sample) in a packet. */ - HOOK_ASYNC, /**< Called asynchronously with fixed rate (see path::rate). */ + HOOK_PRE = 1 << 3, /**< Called when a new packet of messages (samples) was received. */ + HOOK_POST = 1 << 4, /**< Called after each message (sample) of a packet was processed. */ + HOOK_MSG = 1 << 5, /**< Called for each message (sample) in a packet. */ + HOOK_ASYNC = 1 << 6, /**< Called asynchronously with fixed rate (see path::rate). */ - HOOK_PERIODIC, /**< Called periodically. Period is set by global 'stats' option in the configuration file. */ - - HOOK_MAX + HOOK_PERIODIC = 1 << 7, /**< Called periodically. Period is set by global 'stats' option in the configuration file. */ + + HOOK_INIT = 1 << 8, /**< Called to allocate and init hook-private data */ + HOOK_DEINIT = 1 << 9, /**< Called to free hook-private data */ + + /** @{ Classes of hooks */ + /** Internal hooks are mandatory. */ + HOOK_INTERNAL = 1 << 16, + /** Hooks which are using private data must allocate and free them propery. */ + HOOK_PRIVATE = HOOK_INIT | HOOK_DEINIT, + /** All path related actions */ + HOOK_PATH = HOOK_PATH_START | HOOK_PATH_STOP | HOOK_PATH_RESTART, + /** Hooks which are used to collect statistics. */ + HOOK_STATS = HOOK_INTERNAL | HOOK_PRIVATE | HOOK_PATH | HOOK_MSG | HOOK_PERIODIC, + /** All hooks */ + HOOK_ALL = HOOK_INTERNAL - 1 + /** @} */ }; /** Descriptor for user defined hooks. See hooks[]. */ struct hook { - /** The unique name of this hook. This must be the first member! */ - const char *name; - int priority; - hook_cb_t callback; - enum hook_type type; + const char *name; /**< The unique name of this hook. This must be the first member! */ + const char *parameter; /**< A parameter string for this hook. Can be used to configure the hook behaviour. */ + int priority; /**< A priority to change the order of execution within one type of hook */ + enum hook_type type; /**< The type of the hook as a bitfield */ + void *private; /**< Private data for this hook. This pointer can be used to pass data between consecutive calls of the callback. */ + hook_cb_t cb; /**< The hook callback function as a function pointer. */ }; /** The following prototypes are example hooks @@ -76,36 +94,42 @@ struct hook { * @{ */ -/** Example hook: Drop messages whose values are similiar to the previous ones */ -int hook_deduplicate(struct path *); - /** Example hook: Print the message. */ -int hook_print(struct path *p); +int hook_print(struct path *p, struct hook *h, int when); /** Example hook: Drop messages. */ -int hook_decimate(struct path *p); +int hook_decimate(struct path *p, struct hook *h, int when); -/** Example hook: Convert the message values to fixed precision. */ -int hook_tofixed(struct path *p); +/** Example hook: Convert the values of a message between fixed (integer) and floating point representation. */ +int hook_convert(struct path *p, struct hook *h, int when); /** Example hook: overwrite timestamp of message. */ -int hook_ts(struct path *p); +int hook_ts(struct path *p, struct hook *h, int when); /** Example hook: Finite-Impulse-Response (FIR) filter. */ -int hook_fir(struct path *p); +int hook_fir(struct path *p, struct hook *h, int when); -/** Example hook: Discrete Fourier Transform */ -int hook_dft(struct path *p); +/** Example hook: drop first samples after simulation restart */ +int hook_skip_first(struct path *p, struct hook *h, int when); -/* The following prototypes are core hook functions */ +/** Example hook: Skip messages whose values are similiar to the previous ones */ +int hook_skip_unchanged(struct path *p, struct hook *h, int when); + +/* The following hooks are used to implement core functionality */ /** Core hook: verify message headers. Invalid messages will be dropped. */ -int hook_verify(struct path *p); +int hook_verify(struct path *p, struct hook *h, int when); /** Core hook: reset the path in case a new simulation was started. */ -int hook_restart(struct path *p); +int hook_restart(struct path *p, struct hook *h, int when); /** Core hook: check if sequence number is correct. Otherwise message will be dropped */ -int hook_drop(struct path *p); +int hook_drop(struct path *p, struct hook *h, int when); + +/** Core hook: collect statistics */ +int hook_stats(struct path *p, struct hook *h, int when); + +/** Core hook: send path statistics to another node */ +int hook_stats_send(struct path *p, struct hook *h, int when); #endif /** _HOOKS_H_ @} @} */ diff --git a/server/include/path.h b/server/include/path.h index 6838fa7ad..6722e9374 100644 --- a/server/include/path.h +++ b/server/include/path.h @@ -40,7 +40,7 @@ struct path /** List of all outgoing nodes */ struct list destinations; /** List of function pointers to hooks */ - struct list hooks[HOOK_MAX]; + struct list hooks; /** Timer file descriptor for fixed rate sending */ int tfd; @@ -119,15 +119,6 @@ int path_start(struct path *p); */ int path_stop(struct path *p); - -/** Reset internal counters and histogram of a path. - * - * @param p A pointer to the path structure. - * @retval 0 Success. Everything went well. - * @retval <0 Error. Something went wrong. - */ -int path_reset(struct path *p); - /** Show some basic statistics for a path. * * @param p A pointer to the path structure. diff --git a/server/include/stats.h b/server/include/stats.h deleted file mode 100644 index c0476c4f1..000000000 --- a/server/include/stats.h +++ /dev/null @@ -1,36 +0,0 @@ -/** Hook functions to collect statistics - * - * @file - * @author Steffen Vogel - * @copyright 2014-2015, Institute for Automation of Complex Power Systems, EONERC - * This file is part of S2SS. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - *********************************************************************************/ - -#ifndef _STATS_H_ -#define _STATS_H_ - -/* Forward declarations */ -struct path; - -/** Print a table header for statistics printed by stats_line() */ -void stats_header(); - -/** Print a single line of stats including received, sent, invalid and dropped packet counters */ -int stats_line(struct path *p); - -int stats_show(struct path *p); - -/** Update histograms */ -int stats_collect(struct path *p); - -/** Create histograms */ -int stats_start(struct path *p); - -/** Destroy histograms */ -int stats_stop(struct path *p); - -/** Reset all statistic counters to zero */ -int stats_reset(struct path *p); - -#endif /* _STATS_H_ */ \ No newline at end of file diff --git a/server/src/cfg.c b/server/src/cfg.c index a0a59bfd1..24fcdda6e 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -124,7 +124,10 @@ int config_parse_path(config_setting_t *cfg, /* Optional settings */ config_setting_t *cfg_hook = config_setting_get_member(cfg, "hook"); if (cfg_hook) - config_parse_hooklist(cfg_hook, p->hooks); + config_parse_hooklist(cfg_hook, &p->hooks); + + /* Initialize hooks and their private data / parameters */ + path_run_hook(p, HOOK_INIT); if (!config_setting_lookup_bool(cfg, "enabled", &enabled)) enabled = 1; @@ -223,38 +226,14 @@ int config_parse_nodelist(config_setting_t *cfg, struct list *list, struct list } int config_parse_hooklist(config_setting_t *cfg, struct list *list) { - const char *str; - const struct hook *hook; - switch (config_setting_type(cfg)) { case CONFIG_TYPE_STRING: - str = config_setting_get_string(cfg); - if (str) { - hook = list_lookup(&hooks, str); - if (hook) - list_insert(&list[hook->type], hook->priority, hook->callback); - else - cerror(cfg, "Unknown hook function '%s'", str); - } - else - cerror(cfg, "Invalid hook function"); + config_parse_hook(cfg, list); break; case CONFIG_TYPE_ARRAY: - for (int i = 0; itype], hook->priority, hook->callback); - else - cerror(elm, "Invalid hook function '%s'", str); - } - else - cerror(cfg, "Invalid hook function"); - } + for (int i = 0; i < config_setting_length(cfg); i++) + config_parse_hook(config_setting_get_elem(cfg, i), list); break; default: @@ -264,6 +243,31 @@ int config_parse_hooklist(config_setting_t *cfg, struct list *list) { return 0; } +int config_parse_hook(config_setting_t *cfg, struct list *list) +{ + struct hook *hook, *copy; + const char *name = config_setting_get_string(cfg); + if (!name) + cerror(cfg, "Invalid hook function"); + + char *param = strchr(name, ':'); + if (param) { /* Split hook line */ + *param = '\0'; + param++; + } + + hook = list_lookup(&hooks, name); + if (!hook) + cerror(cfg, "Unknown hook function '%s'", name); + + copy = memdup(hook, sizeof(struct hook)); + copy->parameter = param; + + list_push(list, copy); + + return 0; +} + int config_parse_node(config_setting_t *cfg, struct list *nodes, struct settings *set) { const char *type; diff --git a/server/src/hooks.c b/server/src/hooks.c index fa679d418..46bc992de 100644 --- a/server/src/hooks.c +++ b/server/src/hooks.c @@ -22,61 +22,28 @@ #include "path.h" #include "utils.h" -/* Some hooks can be configured by constants in te file "config.h" */ +extern struct list nodes; struct list hooks; -REGISTER_HOOK("deduplicate", 99, hook_deduplicate, HOOK_DEDUP_TYPE) -int hook_deduplicate(struct path *p) -{ - int ret = 0; -#if HOOK_DEDUP_TYPE == HOOK_ASYNC - /** Thread local storage (TLS) is used to maintain a copy of the last run of the hook */ - static __thread struct msg previous = MSG_INIT(0); - struct msg *prev = &previous; -#else - struct msg *prev = p->previous; -#endif - struct msg *cur = p->current; - - for (int i = 0; i < MIN(cur->length, prev->length); i++) { - if (fabs(cur->data[i].f - prev->data[i].f) > HOOK_DEDUP_TRESH) - goto out; - } - - ret = -1; /* no appreciable change in values, we will drop the packet */ - -out: -#if HOOK_DEDUP_TYPE == HOOK_ASYNC - memcpy(prev, cur, sizeof(struct msg)); /* save current message for next run */ -#endif - return ret; -} - REGISTER_HOOK("print", 99, hook_print, HOOK_MSG) -int hook_print(struct path *p) +int hook_print(struct path *p, struct hook *h, int when) { struct msg *m = p->current; - struct timespec ts = MSG_TS(m); + double offset = time_delta(&MSG_TS(m), &p->ts_recv); + int flags = MSG_PRINT_ALL; - msg_fprint(stdout, m, MSG_PRINT_ALL, time_delta(&ts, &p->ts_recv)); - - return 0; -} - -REGISTER_HOOK("tofixed", 99, hook_tofixed, HOOK_MSG) -int hook_tofixed(struct path *p) -{ - struct msg *m = p->current; - - for (int i = 0; i < m->length; i++) - m->data[i].i = m->data[i].f * 1e3; + /* We dont show the offset if its to large */ + if (offset > 1e9) + flags &= ~MSG_PRINT_OFFSET; + + msg_fprint(stdout, m, flags, offset); return 0; } REGISTER_HOOK("ts", 99, hook_ts, HOOK_MSG) -int hook_ts(struct path *p) +int hook_ts(struct path *p, struct hook *h, int when) { struct msg *m = p->current; @@ -86,59 +53,223 @@ int hook_ts(struct path *p) return 0; } -REGISTER_HOOK("fir", 99, hook_fir, HOOK_MSG) -int hook_fir(struct path *p) -{ - /** Coefficients for simple FIR-LowPass: - * F_s = 1kHz, F_pass = 100 Hz, F_block = 300 - * - * Tip: Use MATLAB's filter design tool and export coefficients - * with the integrated C-Header export - */ - static const double coeffs[] = HOOK_FIR_COEFFS; +REGISTER_HOOK("skip_unchanged", 99, hook_skip_unchanged, HOOK_PRIVATE | HOOK_ASYNC) +int hook_skip_unchanged(struct path *p, struct hook *h, int when) +{ + struct private { + double threshold; + struct msg previous; + } *private = h->private; - /** Per path thread local storage for unfiltered sample values. - * The message ringbuffer (p->pool & p->current) will contain filtered data! - */ - static __thread double *past = NULL; - - /** @todo Avoid dynamic allocation at runtime */ - if (!past) - alloc(p->poolsize * sizeof(double)); - - - /* Current value of interest */ - float *cur = &p->current->data[HOOK_FIR_INDEX].f; - - /* Save last sample, unfiltered */ - past[p->received % p->poolsize] = *cur; + switch (when) { + case HOOK_INIT: + private = h->private = alloc(sizeof(struct private)); + + if (!h->parameter) + error("Missing parameter for hook 'deduplication'"); - /* Reset accumulator */ - *cur = 0; + private->threshold = strtof(h->parameter, NULL); + if (!private->threshold) + error("Failed to parse parameter '%s' for hook 'deduplication'", h->parameter); + break; + + case HOOK_DEINIT: + free(private); + break; + + case HOOK_ASYNC: { + int ret = 0; + + struct msg *prev = &private->previous; + struct msg *cur = p->current; - /* FIR loop */ - for (int i = 0; i < MIN(ARRAY_LEN(coeffs), p->poolsize); i++) - *cur += coeffs[i] * past[p->received+p->poolsize-i]; + for (int i = 0; i < MIN(cur->length, prev->length); i++) { + if (fabs(cur->data[i].f - prev->data[i].f) > private->threshold) + goto out; + } + + ret = -1; /* no appreciable change in values, we will drop the packet */ + + out: memcpy(prev, cur, sizeof(struct msg)); /* save current message for next run */ + + return ret; + } + } + + return 0; +} + +REGISTER_HOOK("convert", 99, hook_convert, HOOK_PRIVATE | HOOK_MSG) +int hook_convert(struct path *p, struct hook *h, int when) +{ + struct private { + enum { TO_FIXED, TO_FLOAT } mode; + } *private = h->private; + + switch (when) { + case HOOK_INIT: + private = h->private = alloc(sizeof(struct private)); + + if (!h->parameter) + error("Missing parameter for hook 'deduplication'"); + + if (!strcmp(h->parameter, "fixed")) + private->mode = TO_FIXED; + else if (!strcmp(h->parameter, "float")) + private->mode = TO_FLOAT; + else + error("Invalid parameter '%s' for hook 'convert'", h->parameter); + break; + + case HOOK_DEINIT: + free(private); + break; + + case HOOK_MSG: { + struct msg *m = p->current; + + for (int i = 0; i < m->length; i++) { + switch (private->mode) { + /** @todo allow precission to be configured via parameter */ + case TO_FIXED: m->data[i].i = m->data[i].f * 1e3; break; + case TO_FLOAT: m->data[i].f = m->data[i].i; break; + } + } + break; + } + } return 0; } -REGISTER_HOOK("decimate", 99, hook_decimate, HOOK_POST) -int hook_decimate(struct path *p) +REGISTER_HOOK("fir", 99, hook_fir, HOOK_PRIVATE | HOOK_MSG) +int hook_fir(struct path *p, struct hook *h, int when) { - /* Only sent every HOOK_DECIMATE_RATIO'th message */ - return p->received % HOOK_DECIMATE_RATIO; + /** @todo make this configurable via hook parameters */ + const static double coeffs[] = HOOK_FIR_COEFFS; + + struct private { + double *coeffs; + double *history; + int index; + } *private = h->private; + + switch (when) { + case HOOK_INIT: + if (!h->parameter) + error("Missing parameter for hook 'fir'"); + + private = h->private = alloc(sizeof(struct private)); + + private->coeffs = memdup(coeffs, sizeof(coeffs)); + private->history = alloc(sizeof(coeffs)); + + private->index = strtol(h->parameter, NULL, 10); + if (!private->index) + error("Invalid parameter '%s' for hook 'fir'", h->parameter); + break; + + case HOOK_DEINIT: + free(private->coeffs); + free(private->history); + free(private); + break; + + case HOOK_MSG: { + /* Current value of interest */ + float *cur = &p->current->data[private->index].f; + + /* Save last sample, unfiltered */ + private->history[p->received % p->poolsize] = *cur; + + /* Reset accumulator */ + *cur = 0; + + /* FIR loop */ + for (int i = 0; i < MIN(ARRAY_LEN(coeffs), p->poolsize); i++) + *cur += private->coeffs[i] * private->history[p->received+p->poolsize-i]; + break; + } + } + + return 0; } -REGISTER_HOOK("dft", 99, hook_dft, HOOK_POST) -int hook_dft(struct path *p) +REGISTER_HOOK("decimate", 99, hook_decimate, HOOK_PRIVATE | HOOK_POST) +int hook_decimate(struct path *p, struct hook *h, int when) { - return 0; /** @todo Implement */ + struct private { + long ratio; + } *private = h->private; + + switch (when) { + case HOOK_INIT: + if (!h->parameter) + error("Missing parameter for hook 'decimate'"); + + private = h->private = alloc(sizeof(struct private)); + + private->ratio = strtol(h->parameter, NULL, 10); + if (!private->ratio) + error("Invalid parameter '%s' for hook 'decimate'", h->parameter); + break; + + case HOOK_DEINIT: + free(private); + break; + + case HOOK_POST: + return p->received % private->ratio; + } + + return 0; } -/** System hooks */ +REGISTER_HOOK("skip_first", 99, hook_skip_first, HOOK_PRIVATE | HOOK_POST | HOOK_PATH ) +int hook_skip_first(struct path *p, struct hook *h, int when) +{ + struct private { + double wait; /**< Number of seconds to wait until first message is not skipped */ + struct timespec started; /**< Timestamp of last simulation restart */ + } *private = h->private; + + switch (when) { + case HOOK_INIT: + if (!h->parameter) + error("Missing parameter for hook 'skip_first'"); -int hook_restart(struct path *p) + private = h->private = alloc(sizeof(struct private)); + + private->wait = strtof(h->parameter, NULL); + if (!private->wait) + error("Invalid parameter '%s' for hook 'skip_first'", h->parameter); + break; + + case HOOK_DEINIT: + free(private); + break; + + case HOOK_PATH_RESTART: + private->started = p->ts_recv; + break; + + case HOOK_PATH_START: + clock_gettime(CLOCK_REALTIME, &private->started); + break; + + case HOOK_POST: { + double delta = time_delta(&private->started, &p->ts_recv); + return delta < private->wait + ? -1 /* skip */ + : 0; /* send */ + } + } + + return 0; +} + +REGISTER_HOOK("restart", 1, hook_restart, HOOK_INTERNAL | HOOK_MSG) +int hook_restart(struct path *p, struct hook *h, int when) { if (p->current->sequence == 0 && p->previous->sequence <= UINT32_MAX - 32) { @@ -147,26 +278,34 @@ int hook_restart(struct path *p) buf, p->previous->sequence, p->current->sequence); free(buf); - path_reset(p); + p->sent = + p->invalid = + p->skipped = + p->dropped = 0; + p->received = 1; + + if (path_run_hook(p, HOOK_PATH_RESTART)) + return -1; } return 0; } -int hook_verify(struct path *p) +REGISTER_HOOK("verify", 2, hook_verify, HOOK_INTERNAL | HOOK_MSG) +int hook_verify(struct path *p, struct hook *h, int when) { int reason = msg_verify(p->current); if (reason) { p->invalid++; - warn("Received invalid message (reason=%d)", reason); - + warn("Received invalid message (reason = %d)", reason); return -1; } return 0; } -int hook_drop(struct path *p) +REGISTER_HOOK("drop", 3, hook_drop, HOOK_INTERNAL | HOOK_MSG) +int hook_drop(struct path *p, struct hook *h, int when) { int dist = p->current->sequence - (int32_t) p->previous->sequence; if (dist <= 0 && p->received > 1) { @@ -176,3 +315,101 @@ int hook_drop(struct path *p) else return 0; } + +REGISTER_HOOK("stats", 2, hook_stats, HOOK_STATS) +int hook_stats(struct path *p, struct hook *h, int when) +{ + switch (when) { + case HOOK_INIT: + /** @todo Allow configurable bounds for histograms */ + hist_create(&p->hist_sequence, -HIST_SEQ, +HIST_SEQ, 1); + hist_create(&p->hist_delay, 0, 2, 100e-3); + hist_create(&p->hist_gap, 0, 40e-3, 1e-3); + break; + + case HOOK_DEINIT: + hist_destroy(&p->hist_sequence); + hist_destroy(&p->hist_delay); + hist_destroy(&p->hist_gap); + break; + + case HOOK_MSG: { + struct msg *prev = p->previous, *cur = p->current; + + int dist = cur->sequence - (int32_t) prev->sequence; + double delay = time_delta(&p->ts_recv, &MSG_TS(cur)); + double gap = time_delta(&MSG_TS(prev), &MSG_TS(cur)); + + hist_put(&p->hist_sequence, dist); + hist_put(&p->hist_delay, delay); + hist_put(&p->hist_gap, gap); + break; + } + + case HOOK_PATH_STOP: + if (p->hist_delay.total) { info("One-way delay (received):"); hist_print(&p->hist_delay); } + if (p->hist_gap.total) { info("Message gap time:"); hist_print(&p->hist_gap); } + if (p->hist_sequence.total) { info("Sequence number gaps:"); hist_print(&p->hist_sequence); } + break; + + case HOOK_PATH_RESTART: + hist_reset(&p->hist_sequence); + hist_reset(&p->hist_delay); + hist_reset(&p->hist_gap); + break; + + case HOOK_PERIODIC: { + char *buf = path_print(p); + info("%-32s : %-8u %-8u %-8u %-8u %-8u", buf, p->sent, p->received, p->dropped, p->skipped, p->invalid); + free(buf); + break; + } + } + + return 0; +} + +REGISTER_HOOK("stats_send", 99, hook_stats_send, HOOK_PRIVATE | HOOK_MSG) +int hook_stats_send(struct path *p, struct hook *h, int when) +{ + struct private { + struct node *dest; + int ratio; + } *private = h->private; + + switch (when) { + case HOOK_INIT: + if (!h->parameter) + error("Missing parameter for hook 'stats_send'"); + + private = h->private = alloc(sizeof(struct private)); + + private->dest = list_lookup(&nodes, h->parameter); + if (!private->dest) + error("Invalid destination node '%s' for hook 'stats_send'", h->parameter); + break; + + case HOOK_DEINIT: + free(private); + break; + + case HOOK_MSG: { + struct msg m = MSG_INIT(0); + + m.data[m.length++].f = p->sent; + m.data[m.length++].f = p->received; + m.data[m.length++].f = p->invalid; + m.data[m.length++].f = p->skipped; + m.data[m.length++].f = p->dropped; + m.data[m.length++].f = p->hist_delay.last, + m.data[m.length++].f = p->hist_gap.last; + + /* Send single message with statistics to destination node */ + node_write_single(private->dest, &m); + + break; + } + } + + return 0; +} \ No newline at end of file diff --git a/server/src/path.c b/server/src/path.c index 85a7f80a7..535c9ffbf 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -13,7 +13,6 @@ #include "path.h" #include "timing.h" #include "config.h" -#include "stats.h" #ifndef sigev_notify_thread_id #define sigev_notify_thread_id _sigev_un._tid @@ -42,8 +41,9 @@ static void path_write(struct path *p) int path_run_hook(struct path *p, enum hook_type t) { int ret = 0; - ret += ((hook_cb_t) it->ptr)(p); list_foreach(struct hook *h, &p->hooks) { + if (h->type & t) + ret += ((hook_cb_t) h->cb)(p, h, t); } return ret; @@ -124,6 +124,12 @@ int path_start(struct path *p) char *buf = path_print(p); info("Starting path: %s (poolsize = %u, msgsize = %u, #hooks = %zu)", buf, p->poolsize, p->msgsize, list_length(&p->hooks)); free(buf); + + /* We sort the hooks according to their priority before starting the path */ + int hook_cmp(const void *a, const void *b) { + return ((struct hook *) a)->priority - ((struct hook *) b)->priority; + } + list_sort(&p->hooks, hook_cmp); if (path_run_hook(p, HOOK_PATH_START)) return -1; @@ -188,55 +194,27 @@ char * path_print(struct path *p) return buf; } -int path_reset(struct path *p) -{ - if (path_run_hook(p, HOOK_PATH_RESTART)) - return -1; - - p->sent = - p->received = - p->invalid = - p->skipped = - p->dropped = 0; - - return 0; -} - struct path * path_create() { struct path *p = alloc(sizeof(struct path)); list_init(&p->destinations, NULL); + list_init(&p->hooks, free); - for (int i = 0; i < HOOK_MAX; i++) - list_init(&p->hooks[i], NULL); - -#define hook_add(type, priority, cb) list_insert(&p->hooks[type], priority, cb) - - hook_add(HOOK_MSG, 1, hook_verify); - hook_add(HOOK_MSG, 2, hook_restart); - hook_add(HOOK_MSG, 3, hook_drop); - hook_add(HOOK_MSG, 4, stats_collect); - - hook_add(HOOK_PATH_START, 1, stats_start); - - hook_add(HOOK_PATH_STOP, 2, stats_show); - hook_add(HOOK_PATH_STOP, 3, stats_stop); - - hook_add(HOOK_PATH_RESTART, 1, stats_line); - hook_add(HOOK_PATH_RESTART, 3, stats_reset); - - hook_add(HOOK_PERIODIC, 1, stats_line); + list_foreach(struct hook *h, &hooks) { + if (h->type & HOOK_INTERNAL) + list_push(&p->hooks, memdup(h, sizeof(*h))); + } return p; } void path_destroy(struct path *p) { + path_run_hook(p, HOOK_DEINIT); + list_destroy(&p->destinations); - - for (int i = 0; i < HOOK_MAX; i++) - list_destroy(&p->hooks[i]); + list_destroy(&p->hooks); free(p->pool); free(p); diff --git a/server/src/server.c b/server/src/server.c index ba21a6443..2f6d6c5cb 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -19,7 +19,6 @@ #include "cfg.h" #include "path.h" #include "node.h" -#include "stats.h" #include "checks.h" #ifdef ENABLE_OPAL_ASYNC @@ -194,7 +193,9 @@ int main(int argc, char *argv[]) /* Run! */ if (settings.stats > 0) { - stats_header(); + info("%-32s : %-8s %-8s %-8s %-8s %-8s", + "Source " MAG("=>") " Destination", "#Sent", "#Recv", "#Drop", "#Skip", "#Invalid"); + line(); do list_foreach(struct path *p, &paths) { usleep(settings.stats * 1e6); diff --git a/server/src/stats.c b/server/src/stats.c deleted file mode 100644 index 7bb2b84dc..000000000 --- a/server/src/stats.c +++ /dev/null @@ -1,80 +0,0 @@ -/** Hook functions to collect statistics - * - * @file - * @author Steffen Vogel - * @copyright 2014-2015, Institute for Automation of Complex Power Systems, EONERC - * This file is part of S2SS. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - *********************************************************************************/ - -#include "stats.h" -#include "path.h" -#include "timing.h" -#include "utils.h" - -void stats_header() -{ - info("%-32s : %-8s %-8s %-8s %-8s %-8s", - "Source " MAG("=>") " Destination", "#Sent", "#Recv", "#Drop", "#Skip", "#Invalid"); - line(); -} - -int stats_line(struct path *p) -{ - char *buf = path_print(p); - info("%-32s : %-8u %-8u %-8u %-8u %-8u", buf, p->sent, p->received, p->dropped, p->skipped, p->invalid); - free(buf); - - return 0; -} - -int stats_show(struct path *p) -{ - if (p->hist_delay.total) { info("One-way delay:"); hist_print(&p->hist_delay); } - if (p->hist_gap.total) { info("Message gap time:"); hist_print(&p->hist_gap); } - if (p->hist_sequence.total) { info("Sequence number gaps:"); hist_print(&p->hist_sequence); } - - return 0; -} - -int stats_collect(struct path *p) -{ - int dist = p->current->sequence - (int32_t) p->previous->sequence; - - struct timespec ts1 = MSG_TS(p->current); - struct timespec ts2 = MSG_TS(p->previous); - - hist_put(&p->hist_sequence, dist); - hist_put(&p->hist_delay, time_delta(&ts1, &p->ts_recv)); - hist_put(&p->hist_gap, time_delta(&ts2, &ts1)); - - return 0; -} - -int stats_start(struct path *p) -{ - /** @todo Allow configurable bounds for histograms */ - hist_create(&p->hist_sequence, -HIST_SEQ, +HIST_SEQ, 1); - hist_create(&p->hist_delay, 0, 2, 100e-3); - hist_create(&p->hist_gap, 0, 40e-3, 1e-3); - - return 0; -} - -int stats_stop(struct path *p) -{ - hist_destroy(&p->hist_sequence); - hist_destroy(&p->hist_delay); - hist_destroy(&p->hist_gap); - - return 0; -} - -int stats_reset(struct path *p) -{ - hist_reset(&p->hist_sequence); - hist_reset(&p->hist_delay); - hist_reset(&p->hist_gap); - - return 0; -} From b0d444dc28abbdc20fc21c92d2404ce8e4c37ba3 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 15:48:55 +0200 Subject: [PATCH 23/81] RT_PREEMPT => PREEMPT_RT --- documentation/Mainpage.md | 2 +- documentation/Tuning.md | 2 +- server/include/checks.h | 4 ++-- server/src/checks.c | 2 +- server/src/server.c | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/documentation/Mainpage.md b/documentation/Mainpage.md index 455c0e5b2..97809857e 100644 --- a/documentation/Mainpage.md +++ b/documentation/Mainpage.md @@ -23,7 +23,7 @@ Furthermore, it collects statistics, monitors the quality of service and handles For optimal performance the server is implemented in low-level C and makes use of several Linux-specific realtime features. The primary design goal was to make the behaviour of the system as deterministic as possible. -Therefore, it's advisable to run the server component on a [RT_PREEMPT](https://rt.wiki.kernel.org/index.php/CONFIG_PREEMPT_RT_Patch) patched version of Linux. In our environment, we use Fedora-based distribution which has been stripped to the bare minimum (no GUI, only a few background processes). +Therefore, it's advisable to run the server component on a [PREEMPT_RT](https://rt.wiki.kernel.org/index.php/CONFIG_PREEMPT_RT_Patch) patched version of Linux. In our environment, we use Fedora-based distribution which has been stripped to the bare minimum (no GUI, only a few background processes). The server is a multi-threaded application. diff --git a/documentation/Tuning.md b/documentation/Tuning.md index 1e9769b92..5132b249e 100644 --- a/documentation/Tuning.md +++ b/documentation/Tuning.md @@ -3,7 +3,7 @@ ## Operating System and Kernel For minimum latency several kernel and driver settings can be optimized. -A [RT-preempt patched Linux](https://rt.wiki.kernel.org/index.php/Main_Page) kernel is recommended. +A [PREEMPT_RT patched Linux](https://rt.wiki.kernel.org/index.php/Main_Page) kernel is recommended. Precompiled kernels for Fedora can be found here: http://ccrma.stanford.edu/planetccrma/software/ - Map NIC IRQs => see setting `affinity` diff --git a/server/include/checks.h b/server/include/checks.h index 06c0181cd..d0ce09a43 100644 --- a/server/include/checks.h +++ b/server/include/checks.h @@ -9,14 +9,14 @@ #ifndef _CHECKS_H_ #define _CHECKS_H_ -/** Checks for realtime (RT_PREEMPT) patched kernel. +/** Checks for realtime (PREEMPT_RT) patched kernel. * * See https://rt.wiki.kernel.org * * @retval 0 Kernel is patched. * @reval <>0 Kernel is not patched. */ -int check_kernel_rtpreempt(); +int check_kernel_rt(); /** Check if kernel command line contains "isolcpus=" option. * diff --git a/server/src/checks.c b/server/src/checks.c index e0e593492..c3d3dbc8c 100644 --- a/server/src/checks.c +++ b/server/src/checks.c @@ -34,7 +34,7 @@ int check_kernel_version() return version_compare(¤t, &required) < 0; } -int check_kernel_rtpreempt() +int check_kernel_rt() { return access(SYSFS_PATH "/kernel/realtime", R_OK); } diff --git a/server/src/server.c b/server/src/server.c index 2f6d6c5cb..0cb503380 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -61,8 +61,8 @@ static void realtime_init() { INDENT if (check_kernel_cmdline()) warn("You should reserve some cores for the server (see 'isolcpus')"); - if (check_kernel_rtpreempt()) - warn("We recommend to use an RT_PREEMPT patched kernel!"); + if (check_kernel_rt()) + warn("We recommend to use an PREEMPT_RT patched kernel!"); /* Use FIFO scheduler with real time priority */ if (settings.priority) { From 43f87d47e946e66c8d005c9712c730955cde9941 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 15:49:26 +0200 Subject: [PATCH 24/81] improved error handling in path thread --- server/src/path.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/path.c b/server/src/path.c index 535c9ffbf..8080231a2 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -79,6 +79,10 @@ static void * path_run(void *arg) for(;;) { /* Receive message */ int recv = node_read(p->in, p->pool, p->poolsize, p->received, p->in->combine); + if (recv < 0) + error("Failed to receive message from node '%s'", p->in->name); + else if (recv == 0) + continue; /** @todo Replace this timestamp by hardware timestamping */ clock_gettime(CLOCK_REALTIME, &p->ts_recv); From 53d4b1a507a3d3167d3844f99423b7da9678f429 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 17:20:58 +0200 Subject: [PATCH 25/81] made ngsi_request capable to use different operations --- server/src/ngsi.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/server/src/ngsi.c b/server/src/ngsi.c index 1b599bf47..ed4043dc7 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -86,12 +86,15 @@ static size_t ngsi_request_writer(void *contents, size_t size, size_t nmemb, voi return realsize; } -static int ngsi_request(CURL *handle, json_t *content, json_t **response) +static int ngsi_request(CURL *handle, const char *endpoint, const char *operation, json_t *content, json_t **response) { struct ngsi_response chunk = { 0 }; - char *post = json_dumps(content, JSON_INDENT(4)); + char url[128]; + snprintf(url, sizeof(url), "%s/v1/%s", endpoint, operation); + + curl_easy_setopt(handle, CURLOPT_URL, url); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ngsi_request_writer); curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void *) &chunk); curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, strlen(post)); @@ -304,16 +307,14 @@ int ngsi_open(struct node *n) i->headers = curl_slist_append(i->headers, "Accept: application/json"); i->headers = curl_slist_append(i->headers, "Content-Type: application/json"); - snprintf(buf, sizeof(buf), "%s/v1/updateContext", i->endpoint); - curl_easy_setopt(i->curl, CURLOPT_URL, buf); - curl_easy_setopt(i->curl, CURLOPT_SSL_VERIFYPEER, i->ssl_verify); curl_easy_setopt(i->curl, CURLOPT_TIMEOUT_MS, i->timeout * 1e3); curl_easy_setopt(i->curl, CURLOPT_HTTPHEADER, i->headers); /* Create entity and atributes */ json_object_set_new(i->context, "updateAction", json_string("APPEND")); - return ngsi_request(i->curl, i->context, NULL) == 200 ? 0 : -1; + + return ngsi_request(i->curl, i->endpoint, "updateContext", i->context, NULL) == 200 ? 0 : -1; } int ngsi_close(struct node *n) @@ -322,7 +323,7 @@ int ngsi_close(struct node *n) /* Delete attributes */ json_object_set_new(i->context, "updateAction", json_string("DELETE")); - int code = ngsi_request(i->curl, i->context, NULL) == 200 ? 0 : -1; + int code = ngsi_request(i->curl, i->endpoint, "updateContext", i->context, NULL) == 200 ? 0 : -1; curl_easy_cleanup(i->curl); curl_slist_free_all(i->headers); @@ -373,7 +374,7 @@ int ngsi_write(struct node *n, struct msg *pool, int poolsize, int first, int cn json_object_set_new(i->context, "updateAction", json_string("UPDATE")); json_t *response; - int code = ngsi_request(i->curl, i->context, &response); + int code = ngsi_request(i->curl, i->endpoint, "updateContext", i->context, &response); if (code != 200) error("Failed to NGSI update Context request:\n%s", json_dumps(response, JSON_INDENT(4))); From 3886637db9d673a293cc76f1e3a8fd088ac8e724 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 17:21:20 +0200 Subject: [PATCH 26/81] copied missing settings to reverse path as well --- server/src/cfg.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/server/src/cfg.c b/server/src/cfg.c index 24fcdda6e..a90a87fac 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -116,10 +116,10 @@ int config_parse_path(config_setting_t *cfg, if (cfg_out) config_parse_nodelist(cfg_out, &p->destinations, nodes); - if (list_length(&p->destinations) >= 1) - p->out = (struct node *) list_first(&p->destinations); - else + if (list_length(&p->destinations) < 1) cerror(cfg, "Missing output node for path"); + + p->out = (struct node *) list_first(&p->destinations); /* Optional settings */ config_setting_t *cfg_hook = config_setting_get_member(cfg, "hook"); @@ -150,6 +150,8 @@ int config_parse_path(config_setting_t *cfg, node->refcnt++; node->vt->refcnt++; } + + list_push(paths, p); if (reverse) { if (list_length(&p->destinations) > 1) @@ -157,20 +159,30 @@ int config_parse_path(config_setting_t *cfg, struct path *r = path_create(); - r->in = p->out; /* Swap in/out */ + /* Swap in/out */ + r->in = p->out; r->out = p->in; - + list_push(&r->destinations, r->out); - + + /* Increment reference counters */ r->in->refcnt++; - r->out->refcnt++; r->in->vt->refcnt++; + r->out->refcnt++; r->out->vt->refcnt++; + + if (cfg_hook) + config_parse_hooklist(cfg_hook, &r->hooks); + + /* Initialize hooks and their private data / parameters */ + path_run_hook(p, HOOK_INIT); + + r->rate = p->rate; + r->poolsize = p->poolsize; + r->msgsize = p->msgsize; list_push(paths, r); } - - list_push(paths, p); } else { char *buf = path_print(p); From 3c7a52834f0296d3b96d8c69603c9abb86d3ef7d Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 17:32:10 +0200 Subject: [PATCH 27/81] fixed stupid bug in list_destroy --- server/src/list.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/list.c b/server/src/list.c index 4bbdb89f4..e06bd4268 100644 --- a/server/src/list.c +++ b/server/src/list.c @@ -30,8 +30,8 @@ void list_destroy(struct list *l) pthread_mutex_lock(&l->lock); if (l->destructor) { - for (void *e = l->start; e != l->end; e++) - l->destructor(e); + list_foreach(void *p, l) + l->destructor(p); } free(l->start); From a7beb10458d4fa79cf212a9b9d4969459b44b91d Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 9 Oct 2015 17:32:35 +0200 Subject: [PATCH 28/81] made msg_fparse() more robust against strange files --- server/src/msg.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/msg.c b/server/src/msg.c index cd9e7ea04..c8371b45e 100644 --- a/server/src/msg.c +++ b/server/src/msg.c @@ -93,7 +93,9 @@ int msg_fscan(FILE *f, struct msg *m, int *fl, double *off) skip: if (fgets(line, sizeof(line), f) == NULL) return -1; /* An error occured */ - if (line[0] == '#') + /* Skip whitespaces, empty and comment lines */ + for (ptr = line; isblank(*ptr); ptr++); + if (*ptr == '\0' || *ptr == '#') goto skip; /* Mandatory: seconds */ From 3dc38c0c22a919f127ae5bd31e84182e873feb77 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 08:53:24 +0200 Subject: [PATCH 29/81] Fixed msg_fparse() for correct handling of empty lines and superfluous whitespaces --- server/src/file.c | 2 +- server/src/msg.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/file.c b/server/src/file.c index ab60bacf1..299793633 100644 --- a/server/src/file.c +++ b/server/src/file.c @@ -231,7 +231,7 @@ int file_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt values = msg_fscan(f->in, cur, &flags, NULL); if (values < 0) { if (!feof(f->in)) - warn("Failed to parse file of node '%s", n->name); + warn("Failed to parse file of node '%s': reason=%d", n->name, values); return 0; } diff --git a/server/src/msg.c b/server/src/msg.c index c8371b45e..837bd75a6 100644 --- a/server/src/msg.c +++ b/server/src/msg.c @@ -94,7 +94,7 @@ skip: if (fgets(line, sizeof(line), f) == NULL) return -1; /* An error occured */ /* Skip whitespaces, empty and comment lines */ - for (ptr = line; isblank(*ptr); ptr++); + for (ptr = line; isspace(*ptr); ptr++); if (*ptr == '\0' || *ptr == '#') goto skip; From 5dbfb0301b1dd93889f8bdb6231a6b95f5ff5aaf Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 08:46:11 +0200 Subject: [PATCH 30/81] Added overrun-detection for asynchronous / fixed-rate paths (still missing for synchronous paths) --- server/src/hooks.c | 2 +- server/src/path.c | 11 +++++++++-- server/src/server.c | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/server/src/hooks.c b/server/src/hooks.c index 46bc992de..959f17740 100644 --- a/server/src/hooks.c +++ b/server/src/hooks.c @@ -360,7 +360,7 @@ int hook_stats(struct path *p, struct hook *h, int when) case HOOK_PERIODIC: { char *buf = path_print(p); - info("%-32s : %-8u %-8u %-8u %-8u %-8u", buf, p->sent, p->received, p->dropped, p->skipped, p->invalid); + info("%-32s : %-8u %-8u %-8u %-8u %-8u %-8u", buf, p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun); free(buf); break; } diff --git a/server/src/path.c b/server/src/path.c index 8080231a2..bf513969b 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -55,7 +55,14 @@ static void * path_run_async(void *arg) struct path *p = arg; /* Block until 1/p->rate seconds elapsed */ - while (timerfd_wait(p->tfd)) { + for (;;) { + /* Check for overruns */ + uint64_t expir = timerfd_wait(p->tfd); + if (expir > 1) { + p->overrun += expir; + warn("Overrun detected for path: overruns=%llu", expir); + } + if (path_run_hook(p, HOOK_ASYNC)) continue; @@ -76,7 +83,7 @@ static void * path_run(void *arg) p->previous = p->current = p->pool; /* Main thread loop */ - for(;;) { + for (;;) { /* Receive message */ int recv = node_read(p->in, p->pool, p->poolsize, p->received, p->in->combine); if (recv < 0) diff --git a/server/src/server.c b/server/src/server.c index 0cb503380..b835169ed 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -193,8 +193,8 @@ int main(int argc, char *argv[]) /* Run! */ if (settings.stats > 0) { - info("%-32s : %-8s %-8s %-8s %-8s %-8s", - "Source " MAG("=>") " Destination", "#Sent", "#Recv", "#Drop", "#Skip", "#Invalid"); + info("%-32s : %-8s %-8s %-8s %-8s %-8s %-8s", + "Source " MAG("=>") " Destination", "#Sent", "#Recv", "#Drop", "#Skip", "#Invalid", "#Overuns"); line(); do list_foreach(struct path *p, &paths) { From 0d51b310bfd49d46eadd763b096f5481fc756116 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 09:45:12 +0200 Subject: [PATCH 31/81] applied fixes to silence compiler warnings and added new version (now same accross all S2SS components) --- clients/opal/udp/models/send_receive/include/config.h | 4 ++-- clients/opal/udp/models/send_receive/s2ss.mk | 2 +- clients/opal/udp/models/send_receive/src/s2ss.c | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/clients/opal/udp/models/send_receive/include/config.h b/clients/opal/udp/models/send_receive/include/config.h index 95af71795..37b594ea2 100644 --- a/clients/opal/udp/models/send_receive/include/config.h +++ b/clients/opal/udp/models/send_receive/include/config.h @@ -8,8 +8,8 @@ #ifndef _CONFIG_H_ #define _CONFIG_H_ -#define PROGNAME "S2SS" -#define VERSION "0.1" +#define PROGNAME "S2SS-OPAL-UDP" +#define VERSION "0.5" #define MAX_VALUES 64 diff --git a/clients/opal/udp/models/send_receive/s2ss.mk b/clients/opal/udp/models/send_receive/s2ss.mk index 0cf147180..b2b20b766 100644 --- a/clients/opal/udp/models/send_receive/s2ss.mk +++ b/clients/opal/udp/models/send_receive/s2ss.mk @@ -49,7 +49,7 @@ endif INCLUDES = -I. LIBPATH = -L. -CC_OPTS = +CC_OPTS = -std=c99 LD_OPTS = OBJS = s2ss.o msg.o utils.o socket.o diff --git a/clients/opal/udp/models/send_receive/src/s2ss.c b/clients/opal/udp/models/send_receive/src/s2ss.c index 4ac32161a..df23e5a93 100644 --- a/clients/opal/udp/models/send_receive/src/s2ss.c +++ b/clients/opal/udp/models/send_receive/src/s2ss.c @@ -43,6 +43,7 @@ #include "config.h" #include "msg.h" #include "socket.h" +#include "utils.h" /* This is just for initializing the shared memory access to communicate * with the RT-LAB model. It's easier to remember the arguments like this */ @@ -256,7 +257,7 @@ int main(int argc, char *argv[]) pthread_t tid_send, tid_recv; pthread_attr_t attr_send, attr_recv; - OpalPrint("%s: This is a S2SS client\n", PROGNAME); + OpalPrint("%s: This is %s client version %s\n", PROGNAME, PROGNAME, VERSION); /* Check for the proper arguments to the program */ if (argc < 4) { From 784e64fb42bbf9fcfc760112123994fed3bf53c2 Mon Sep 17 00:00:00 2001 From: Marija Stevic Date: Sun, 11 Oct 2015 10:20:12 +0200 Subject: [PATCH 32/81] add info for decimate hook --- server/etc/example.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/etc/example.conf b/server/etc/example.conf index d6e74e6d4..804f81e40 100644 --- a/server/etc/example.conf +++ b/server/etc/example.conf @@ -165,7 +165,7 @@ 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" ] # Same is true for hooks. + hook = [ "print", "decimate:10" ] # Same is true for hooks. # Multipe hook functions are executed in the order they are specified here. } ); From 09801bf220631084b09b944952ff12e4e4cd5493 Mon Sep 17 00:00:00 2001 From: Marija Stevic Date: Sun, 11 Oct 2015 10:20:41 +0200 Subject: [PATCH 33/81] added checklist for AsyncIP exe --- clients/opal/udp_readme.txt | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/clients/opal/udp_readme.txt b/clients/opal/udp_readme.txt index 235c3ad1a..1dbd36f77 100644 --- a/clients/opal/udp_readme.txt +++ b/clients/opal/udp_readme.txt @@ -37,6 +37,31 @@ Development tab -> Compiler -> Compiler Command (makefile) add the following com max umber of values in UDP packets: thereโ€™s a โ€ž#defineโ€œ inside the AsyncIP implementation which must be changed accordingly. The #define is in file: model_directory/include/config.h There you will find a directive called MAX_VALUES. + +--------------------------------------------------- +AsyncIP executable + +- During ***Build the model*** a message should be printed: + ### Created executable: AsyncIP + +- After the simulation stop + AsyncIP may still stay alive after the simulation stop. You have to remove it manually because the next simulation start will not be able to start the new AsyncIP. + +# Kill running AsyncIP on OPAL + +1. Start putty.exe + +2. Connect to OPAL by using the existing profiles + - make sure that you are in the proper folder by + $ ll + +3. Kill all running processes with name 'AsyncIP' + + $ killall AsyncIP + +4. Logout from OPAL + + $ exit + --------------------------------------------------- - From caffe10509aa57cc6ba9d7b1aab7100fbc24f80d Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 10:36:47 +0200 Subject: [PATCH 34/81] hotfix: sequence number integer type was too small. --- clients/opal/udp/models/send_receive/src/s2ss.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/opal/udp/models/send_receive/src/s2ss.c b/clients/opal/udp/models/send_receive/src/s2ss.c index df23e5a93..4fa63cb9d 100644 --- a/clients/opal/udp/models/send_receive/src/s2ss.c +++ b/clients/opal/udp/models/send_receive/src/s2ss.c @@ -79,8 +79,8 @@ static void *SendToIPPort(void *arg) unsigned int SendID = 1; unsigned int ModelState; unsigned int i, n; - unsigned short seq = 0; int nbSend = 0; + uint32_t seq = 0; /* Data from OPAL-RT model */ double mdldata[MSG_VALUES]; From 78bc07b5d544b6c5b2745f0453f129de8f3b9e3a Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 12:59:54 +0200 Subject: [PATCH 35/81] hotfix: initialize histogram for reverse path --- server/src/cfg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/cfg.c b/server/src/cfg.c index a90a87fac..84c310d00 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -175,7 +175,7 @@ int config_parse_path(config_setting_t *cfg, config_parse_hooklist(cfg_hook, &r->hooks); /* Initialize hooks and their private data / parameters */ - path_run_hook(p, HOOK_INIT); + path_run_hook(r, HOOK_INIT); r->rate = p->rate; r->poolsize = p->poolsize; From 01cf61e467d40126e502d22f705be91dbb0bbd18 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 13:22:58 +0200 Subject: [PATCH 36/81] updated Doxygen documentation --- server/include/config.h | 12 ++++-------- server/include/gtfpga.h | 4 +--- server/include/hist.h | 36 ++++++++++++----------------------- server/include/if.h | 12 ++++-------- server/include/ngsi.h | 28 ++++++++++++--------------- server/include/node.h | 42 ++++++++++++++++++++++++++--------------- server/src/server.c | 12 ++++-------- 7 files changed, 64 insertions(+), 82 deletions(-) diff --git a/server/include/config.h b/server/include/config.h index 0afaa36ee..9dc96f0f9 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -65,14 +65,10 @@ /** Global configuration */ struct settings { - /** Process priority (lower is better) */ - int priority; - /** Process affinity of the server and all created threads */ - int affinity; - /** Debug log level */ - int debug; - /** Interval for path statistics. Set to 0 to disable themo disable them. */ - double stats; + int priority; /**< Process priority (lower is better) */ + int affinity; /**< Process affinity of the server and all created threads */ + int debug; /**< Debug log level */ + double stats; /**< Interval for path statistics. Set to 0 to disable themo disable them. */ }; #endif /* _CONFIG_H_ */ diff --git a/server/include/gtfpga.h b/server/include/gtfpga.h index fe20a4192..9f3ce908e 100644 --- a/server/include/gtfpga.h +++ b/server/include/gtfpga.h @@ -38,9 +38,7 @@ struct gtfpga { int fd_mmap; /**< File descriptor for the memory mapped PCI BAR */ void *map; - /* The following descriptor is blocking as long no interrupt was received - * or the timer has not elapsed */ - int fd_irq; /**< File descriptor for the timer */ + int fd_irq; /**< File descriptor for the timer. This is blocking when read(2) until a new IRQ / timer expiration. */ char *name; double rate; diff --git a/server/include/hist.h b/server/include/hist.h index 1f117b088..5c2894d89 100644 --- a/server/include/hist.h +++ b/server/include/hist.h @@ -21,36 +21,24 @@ typedef unsigned hist_cnt_t; /** Histogram structure used to collect statistics. */ struct hist { - /** The distance between two adjacent buckets. */ - double resolution; + double resolution; /**< The distance between two adjacent buckets. */ - /** The value of the highest bucket. */ - double high; - /** The value of the lowest bucket. */ - double low; + double high; /**< The value of the highest bucket. */ + double low; /**< The value of the lowest bucket. */ - /** The highest value observed (may be higher than #high). */ - double highest; - /** The lowest value observed (may be lower than #low). */ - double lowest; - /** The last value which has been put into the buckets */ - double last; + double highest; /**< The highest value observed (may be higher than #high). */ + double lowest; /**< The lowest value observed (may be lower than #low). */ + double last; /**< The last value which has been put into the buckets */ - /** The number of buckets in #data. */ - int length; + int length; /**< The number of buckets in #data. */ - /** Total number of counted values. */ - hist_cnt_t total; - /** The number of values which are higher than #high. */ - hist_cnt_t higher; - /** The number of values which are lower than #low. */ - hist_cnt_t lower; + hist_cnt_t total; /**< Total number of counted values. */ + hist_cnt_t higher; /**< The number of values which are higher than #high. */ + hist_cnt_t lower; /**< The number of values which are lower than #low. */ - /** Pointer to dynamically allocated array of size length. */ - hist_cnt_t *data; + hist_cnt_t *data; /**< Pointer to dynamically allocated array of size length. */ - /** Private variables for online variance calculation */ - double _m[2], _s[2]; + double _m[2], _s[2]; /**< Private variables for online variance calculation */ }; /** Initialize struct hist with supplied values and allocate memory for buckets. */ diff --git a/server/include/if.h b/server/include/if.h index f76daba68..7901867bd 100644 --- a/server/include/if.h +++ b/server/include/if.h @@ -30,16 +30,12 @@ struct rtnl_link; /** Interface data structure */ struct interface { - /** libnl3: Handle of interface */ - struct rtnl_link *nl_link; - /** libnl3: Root prio qdisc */ - struct rtnl_qdisc *tc_qdisc; + struct rtnl_link *nl_link; /**< libnl3: Handle of interface. */ + struct rtnl_qdisc *tc_qdisc; /**< libnl3: Root priority queuing discipline (qdisc). */ - /** List of IRQs of the NIC */ - char irqs[IF_IRQ_MAX]; + char irqs[IF_IRQ_MAX]; /**< List of IRQs of the NIC. */ - /** Linked list of associated sockets */ - struct list sockets; + struct list sockets; /**< Linked list of associated sockets. */ }; /** Add a new interface to the global list and lookup name, irqs... diff --git a/server/include/ngsi.h b/server/include/ngsi.h index 52e321bd3..dd64cc0c0 100644 --- a/server/include/ngsi.h +++ b/server/include/ngsi.h @@ -33,33 +33,29 @@ struct node; struct ngsi { - /** The NGSI context broker endpoint URL. */ - const char *endpoint; - /** An optional authentication token which will be sent as HTTP header. */ - const char *token; + const char *endpoint; /**< The NGSI context broker endpoint URL. */ + const char *token; /**< An optional authentication token which will be sent as HTTP header. */ + + double timeout; /**< HTTP timeout in seconds */ + + int ssl_verify; /**< Boolean flag whether SSL server certificates should be verified or not. */ - /** HTTP timeout in seconds */ - double timeout; - /** Boolean flag whether SSL server certificates should be verified or not. */ - int ssl_verify; - /** Structure of published entitites */ enum ngsi_structure { NGSI_FLAT, NGSI_CHILDREN - } structure; + } structure; /**< Structure of published entitites */ - /** List of HTTP request headers for libcurl */ - struct curl_slist *headers; - /** libcurl handle */ - CURL *curl; - /** The complete JSON tree which will be used for contextUpdate requests */ - json_t *context; /** A mapping between indices of the S2SS messages and the attributes in ngsi::context */ json_t **context_map; /** The number of mappings in ngsi::context_map */ int context_len; + struct curl_slist *headers; /**< List of HTTP request headers for libcurl */ + + CURL *curl; /**< libcurl: handle */ + + json_t *context; /**< The complete JSON tree which will be used for contextUpdate requests */ }; /** Initialize global NGSI settings and maps shared memory regions. diff --git a/server/include/node.h b/server/include/node.h index a2e5dbf5e..82cc1306b 100644 --- a/server/include/node.h +++ b/server/include/node.h @@ -130,10 +130,28 @@ struct node_type { */ int (*write)(struct node *n, struct msg *pool, int poolsize, int first, int cnt); + /** Global initialization per node type. + * + * This callback is invoked once per node-type. + * + * @param argc Number of arguments passed to the server executable (see main()). + * @param argv Array of arguments passed to the server executable (see main()). + * @param set Global settings. + * @retval 0 Success. Everything went well. + * @retval <0 Error. Something went wrong. + */ int (*init)(int argc, char *argv[], struct settings *set); + + /** Global de-initialization per node type. + * + * This callback is invoked once per node-type. + * + * @retval 0 Success. Everything went well. + * @retval <0 Error. Something went wrong. + */ int (*deinit)(); - int refcnt; + int refcnt; /**< Reference counter: how many nodes are using this node-type? */ }; /** The data structure for a node. @@ -143,28 +161,22 @@ struct node_type { */ struct node { - /** A short identifier of the node, only used for configuration and logging */ - char *name; - /** How many paths are sending / receiving from this node? */ - int refcnt; - /** Number of messages to send / recv at once (scatter / gather) */ - int combine; - /** CPU Affinity of this node */ - int affinity; + char *name; /**< A short identifier of the node, only used for configuration and logging */ + int refcnt; /**< How many paths are sending / receiving from this node? */ + int combine; /**< Number of messages to send / recv at once (scatter / gather) */ + int affinity; /**< CPU Affinity of this node */ + + struct node_type *vt; /**< C++ like virtual function call table */ - /** C++ like virtual function call table */ - struct node_type * vt; - /** Virtual data (used by vtable functions) */ union { struct socket *socket; struct opal *opal; struct gtfpga *gtfpga; struct file *file; struct ngsi *ngsi; - }; + }; /** Virtual data (used by vtable functions) */ - /** A pointer to the libconfig object which instantiated this node */ - config_setting_t *cfg; + config_setting_t *cfg; /**< A pointer to the libconfig object which instantiated this node */ }; /** Initialize node type subsystems. diff --git a/server/src/server.c b/server/src/server.c index b835169ed..8eeeeacab 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -25,14 +25,10 @@ #include "opal.h" #endif -/** Linked list of nodes */ -struct list nodes; -/** Linked list of paths */ -struct list paths; -/** The global configuration */ -struct settings settings; -/** libconfig handle */ -static config_t config; +struct list nodes; /**< Linked list of nodes */ +struct list paths; /**< Linked list of paths */ +struct settings settings; /**< The global configuration */ +static config_t config; /**< libconfig handle */ static void quit() { From 7135dcfa69f5a49a35c31dfdab93ac36c654000b Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 13:25:06 +0200 Subject: [PATCH 37/81] renamed log_reset() to log_init() --- server/include/log.h | 2 +- server/src/log.c | 2 +- server/src/receive.c | 2 +- server/src/send.c | 2 +- server/src/server.c | 2 +- server/src/test.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/include/log.h b/server/include/log.h index 55b524563..a16a95d92 100644 --- a/server/include/log.h +++ b/server/include/log.h @@ -45,7 +45,7 @@ void log_outdent(int *); void log_setlevel(int lvl); /** Reset the wallclock of debug messages. */ -void log_reset(); +void log_init(); /** Logs variadic messages to stdout. * diff --git a/server/src/log.c b/server/src/log.c index 006669a25..984385cb5 100644 --- a/server/src/log.c +++ b/server/src/log.c @@ -54,7 +54,7 @@ void log_setlevel(int lvl) debug(10, "Switched to debug level %u", level); } -void log_reset() +void log_init() { clock_gettime(CLOCK_REALTIME, &epoch); debug(10, "Debug clock resetted"); diff --git a/server/src/receive.c b/server/src/receive.c index 2d612d08a..d11a9716b 100644 --- a/server/src/receive.c +++ b/server/src/receive.c @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) list_init(&nodes, (dtor_cb_t) node_destroy); - log_reset(); + log_init(); config_init(&config); config_parse(argv[optind], &config, &set, &nodes, NULL); diff --git a/server/src/send.c b/server/src/send.c index 7c5e57b85..2fc0d8c65 100644 --- a/server/src/send.c +++ b/server/src/send.c @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) list_init(&nodes, (dtor_cb_t) node_destroy); - log_reset(); + log_init(); config_init(&config); config_parse(argv[optind], &config, &set, &nodes, NULL); diff --git a/server/src/server.c b/server/src/server.c index 8eeeeacab..a13a25e50 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -145,7 +145,7 @@ int main(int argc, char *argv[]) char *configfile = (argc == 2) ? argv[1] : "opal-shmem.conf"; - log_reset(); + log_init(); info("This is Simulator2Simulator Server (S2SS)"); info (" Version: %s (built on %s, %s)", BLD(YEL(VERSION)), BLD(MAG(__DATE__)), BLD(MAG(__TIME__))); diff --git a/server/src/test.c b/server/src/test.c index 3864e98de..eeb8e8672 100644 --- a/server/src/test.c +++ b/server/src/test.c @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) sigaction(SIGTERM, &sa_quit, NULL); sigaction(SIGINT, &sa_quit, NULL); - log_reset(); + log_init(); config_init(&config); config_parse(argv[1], &config, &settings, &nodes, NULL); From 28e52c294e0f46a4ef23a7017f56d30127027444 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 13:41:50 +0200 Subject: [PATCH 38/81] added missing overrun field for paths --- server/include/path.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/include/path.h b/server/include/path.h index 6722e9374..c381c3069 100644 --- a/server/include/path.h +++ b/server/include/path.h @@ -90,6 +90,8 @@ struct path unsigned int skipped; /** Counter for dropped messages due to reordering */ unsigned int dropped; + /** Counter of overruns for fixed-rate sending */ + unsigned int overrun; }; /** Create a path by allocating dynamic memory. */ From 0b5358ed4b2fe7a2ffb3317919d78b7b92a951f4 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 14:45:54 +0200 Subject: [PATCH 39/81] added new global 'name' setting --- documentation/Configuration.md | 6 ++++++ server/etc/example.conf | 3 +++ server/include/config.h | 9 +++++---- server/src/cfg.c | 6 ++++++ server/src/ngsi.c | 10 ++++++---- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/documentation/Configuration.md b/documentation/Configuration.md index 4b7fcb212..bb455e022 100644 --- a/documentation/Configuration.md +++ b/documentation/Configuration.md @@ -35,6 +35,12 @@ This technique, also called 'pinning', improves the determinism of the server by The `priority` setting allows to adjust the scheduling priority of the deamon processes. By default, the daemon uses a real-time optimized FIFO scheduling algorithm. +#### `name` *(integer)* + +By default the `name` of a S2SS instance is equalt to the hostname of the machine it is running on. +Some node types are using this name to identify themselves agains their remotes. +An example is the `ngsi` node type which adds a metadata attribute `source` to its updates. + ## Nodes The node section is a **directory** of nodes (clients) which are connected to the S2SS instance. diff --git a/server/etc/example.conf b/server/etc/example.conf index 804f81e40..0c13b8494 100644 --- a/server/etc/example.conf +++ b/server/etc/example.conf @@ -27,6 +27,9 @@ debug = 5; # The level of verbosity for debug messages stats = 3; # The interval in seconds to print path statistics. # A value of 0 disables the statistics. +name = "s2ss-acs" # The name of this S2SS instance. Might by used by node-types + # to identify themselves (default is the hostname). + ############ Dictionary of nodes ############ diff --git a/server/include/config.h b/server/include/config.h index 9dc96f0f9..0eac018c1 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -65,10 +65,11 @@ /** Global configuration */ struct settings { - int priority; /**< Process priority (lower is better) */ - int affinity; /**< Process affinity of the server and all created threads */ - int debug; /**< Debug log level */ - double stats; /**< Interval for path statistics. Set to 0 to disable themo disable them. */ + int priority; /**< Process priority (lower is better) */ + int affinity; /**< Process affinity of the server and all created threads */ + int debug; /**< Debug log level */ + double stats; /**< Interval for path statistics. Set to 0 to disable themo disable them. */ + const char *name; /**< Name of the S2SS instance */ }; #endif /* _CONFIG_H_ */ diff --git a/server/src/cfg.c b/server/src/cfg.c index 84c310d00..84d0785c1 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -8,6 +8,7 @@ #include #include +#include #include "utils.h" #include "list.h" @@ -87,6 +88,11 @@ int config_parse_global(config_setting_t *cfg, struct settings *set) config_setting_lookup_int(cfg, "debug", &set->debug); config_setting_lookup_float(cfg, "stats", &set->stats); + if (!config_setting_lookup_string(cfg, "name", &set->name)) { + set->name = alloc(128); /** @todo missing free */ + gethostname((char *) set->name, 128); + } + log_setlevel(set->debug); return 0; diff --git a/server/src/ngsi.c b/server/src/ngsi.c index ed4043dc7..8a9ec3b46 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -23,6 +23,8 @@ #include "ngsi.h" #include "utils.h" +extern struct settings settings; + static json_t * json_uuid() { char eid[37]; @@ -181,10 +183,10 @@ void ngsi_prepare_context(struct node *n, config_setting_t *mapping) /* Create Metadata for attribute */ json_t *metadatas = json_array(); - json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: s }", - "name", "source", - "type", "string", - "value", "s2ss" + json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: s+ }", + "name", "source", + "type", "string", + "value", "s2ss:", settings.name )); json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: i }", "name", "index", From b1c1d77597186c1ba85d1b4828fdcc92207ccde2 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 14:50:08 +0200 Subject: [PATCH 40/81] added new function to get current time --- server/include/timing.h | 3 +++ server/src/file.c | 6 ++---- server/src/timing.c | 9 +++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/server/include/timing.h b/server/include/timing.h index 88711a3b3..6aafda65e 100644 --- a/server/include/timing.h +++ b/server/include/timing.h @@ -39,6 +39,9 @@ struct timespec time_diff(struct timespec *start, struct timespec *end); /** Get sum of two timespec structs */ struct timespec time_add(struct timespec *start, struct timespec *end); +/** Return current time as a struct timespec. */ +struct timespec time_now(); + /** Return the diffrence off two timestamps as double value in seconds. */ double time_delta(struct timespec *start, struct timespec *end); diff --git a/server/src/file.c b/server/src/file.c index 299793633..f3b626320 100644 --- a/server/src/file.c +++ b/server/src/file.c @@ -65,8 +65,7 @@ char * file_print(struct node *n) if ((f->first.tv_sec || f->first.tv_nsec) && (f->offset.tv_sec || f->offset.tv_nsec)) { - struct timespec eta, now; - clock_gettime(CLOCK_REALTIME, &now); + struct timespec eta, now = time_now(); eta = time_add(&f->first, &f->offset); eta = time_diff(&now, &eta); @@ -145,8 +144,7 @@ int file_open(struct node *n) serror("Failed to create timer"); /* Get current time */ - struct timespec now, eta; - clock_gettime(CLOCK_REALTIME, &now); + struct timespec now = time_now(); /* Get timestamp of first line */ struct msg m; diff --git a/server/src/timing.c b/server/src/timing.c index b0ae18a93..095ddf446 100644 --- a/server/src/timing.c +++ b/server/src/timing.c @@ -31,6 +31,15 @@ uint64_t timerfd_wait_until(int fd, struct timespec *until) return timerfd_wait(fd); } +struct timespec time_now() +{ + struct timespec ts; + + clock_gettime(CLOCK_REALTIME, &ts); + + return ts; +} + struct timespec time_add(struct timespec *start, struct timespec *end) { struct timespec sum = { From 2936757a78f629764e2bf545ad574fbe8b211032 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 14:50:55 +0200 Subject: [PATCH 41/81] added correct fmt string identifier for uint64_t --- server/src/path.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/path.c b/server/src/path.c index bf513969b..38872fecf 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -8,6 +8,7 @@ #include #include +#include #include "utils.h" #include "path.h" @@ -60,7 +61,7 @@ static void * path_run_async(void *arg) uint64_t expir = timerfd_wait(p->tfd); if (expir > 1) { p->overrun += expir; - warn("Overrun detected for path: overruns=%llu", expir); + warn("Overrun detected for path: overruns=%" PRIu64, expir); } if (path_run_hook(p, HOOK_ASYNC)) From c557e4b90469ef98613e8cf451e3bef10fb58068 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 15:00:15 +0200 Subject: [PATCH 42/81] updated example configuration docs --- server/etc/example.conf | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/server/etc/example.conf b/server/etc/example.conf index 0c13b8494..ef817a4eb 100644 --- a/server/etc/example.conf +++ b/server/etc/example.conf @@ -93,20 +93,14 @@ nodes = { in = "logs/file_input.log", # These options specify the path prefix where the the files are stored out = "logs/file_output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3)) - epoch_mode = "direct" # One of: - # direct (default) - # wait - # relative - # absolute - - epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0): - # - epoch_mode = now: The first value is read at: _now_ + epoch seconds. - # - epoch_mode = relative: The first value is read at _start_ + `epoch` seconds. - # - epoch_mode = absolute: The first value is read at epoch seconds after 1970-01-01 00:00:00. + epoch_mode = "direct" # One of: direct (default), wait, relative, absolute + epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0). + # Consult the documentation of a full explanation + - rate = 2.0 # A constant rate at which the lines of the input files should be read - # A missing or zero value will use the timestamp in the first column - # of the file to determine the pause between consecutive lines. + rate = 2.0 # A constant rate at which the lines of the input files should be read + # A missing or zero value will use the timestamp in the first column + # of the file to determine the pause between consecutive lines. }, gtfpga_node = { type = "gtfpga", @@ -168,7 +162,7 @@ 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. + hook = [ "print", "decimate:10" ] # Same is true for hooks. # Multipe hook functions are executed in the order they are specified here. } ); From 908f930b631c816aba79eae0137e927c5c244b7f Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 16:31:48 +0200 Subject: [PATCH 43/81] improved output of histogram plot --- server/src/hist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/hist.c b/server/src/hist.c index b92492fb4..b41b3bd8b 100644 --- a/server/src/hist.c +++ b/server/src/hist.c @@ -143,7 +143,7 @@ void hist_plot(struct hist *h) for (int i = 0; i < h->length; i++) { int bar = HIST_HEIGHT * ((double) h->data[i] / max); - info("%3u | %+5.2e | " "%5u" " | %.*s", i, VAL(h, i), h->data[i], bar, buf); + info("%3u | %+9g | " "%5u" " | %.*s", i, VAL(h, i), h->data[i], bar, buf); } } From 4cf1b227834749b1fff84a195e30b604522e15d3 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 11 Oct 2015 18:13:46 +0200 Subject: [PATCH 44/81] added short example / description of all available hooks --- server/etc/example.conf | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/server/etc/example.conf b/server/etc/example.conf index ef817a4eb..e178c772f 100644 --- a/server/etc/example.conf +++ b/server/etc/example.conf @@ -164,5 +164,20 @@ paths = ( 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'. + ] } ); From 4271c17d68e7561da20abe3092a8a654f43ca83c Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 13:45:05 +0200 Subject: [PATCH 45/81] added sub-second precision for json_date() ISO 8601 --- server/include/utils.h | 8 ++++---- server/src/ngsi.c | 12 +++--------- server/src/utils.c | 29 ++++++++++++++++++++++++----- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/server/include/utils.h b/server/include/utils.h index 4aa77b72d..86730efa8 100644 --- a/server/include/utils.h +++ b/server/include/utils.h @@ -101,6 +101,10 @@ char * strcatf(char **dest, const char *fmt, ...) char * vstrcatf(char **dest, const char *fmt, va_list va) __attribute__ ((format(printf, 2, 0))); +/** Format a struct timespec date similar to strftime() */ +int strftimespec(char *s, size_t max, const char *format, struct timespec *ts) + __attribute__ ((format(strftime, 3, 0))); + /** Convert integer to cpu_set_t. * * @param set A cpu bitmask @@ -129,10 +133,6 @@ int version_compare(struct version *a, struct version *b); /** Parse a dotted version string. */ int version_parse(const char *s, struct version *v); -/** Format a struct timespec date similar to strftime() */ -int strftimespec(char *s, uint max, const char *format, struct timespec *ts) - __attribute__ ((format(strftime, 3, 0))); - /** Check assertion and exit if failed. */ #define assert(exp) do { \ if (!EXPECT(exp, 0)) \ diff --git a/server/src/ngsi.c b/server/src/ngsi.c index 8a9ec3b46..db0445b69 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -38,15 +38,9 @@ static json_t * json_uuid() static json_t * json_date(struct timespec *ts) { - struct timespec tsp; - if (!ts) { - clock_gettime(CLOCK_REALTIME, &tsp); - ts = &tsp; - } - // Example: 2015-09-21T11:42:25+02:00 char date[64]; - strftimespec(date, sizeof(date), "%FT%T%z", ts); + strftimespec(date, sizeof(date), "%FT%T.%u%z", ts); return json_string(date); } @@ -193,10 +187,10 @@ void ngsi_prepare_context(struct node *n, config_setting_t *mapping) "type", "integer", "value", j )); - json_array_append(metadatas, json_pack("{ s: s, s: s, s: o }", + json_array_append(metadatas, json_pack("{ s: s, s: s, s: s }", "name", "timestamp", "type", "date", - "value", json_date(NULL) + "value", "" )); if (i->structure == NGSI_CHILDREN) { diff --git a/server/src/utils.c b/server/src/utils.c index aab070f58..d0fe310b2 100644 --- a/server/src/utils.c +++ b/server/src/utils.c @@ -31,14 +31,33 @@ int version_compare(struct version *a, struct version *b) { return major ? major : minor; } -int strftimespec(char *s, uint max, const char *format, struct timespec *ts) +int strftimespec(char *dst, size_t max, const char *fmt, struct timespec *ts) { - struct tm t; + int s, d; + char fmt2[64], dst2[64]; - if (localtime_r(&(ts->tv_sec), &t) == NULL) - return -1; + for (s=0, d=0; fmt[s] && d < 64;) { + char c = fmt2[d++] = fmt[s++]; + if (c == '%') { + char n = fmt[s]; + if (n == 'u') { + fmt2[d++] = '0'; + fmt2[d++] = '3'; + } + else + fmt2[d++] = '%'; + } + } + + /* Print sub-second part to format string */ + snprintf(dst2, sizeof(dst2), fmt2, ts->tv_nsec / 1000000); - return strftime(s, max, format, &t); + /* Print timestamp */ + struct tm tm; + if (localtime_r(&(ts->tv_sec), &tm) == NULL) + return -1; + + return strftime(dst, max, dst2, &tm); } double box_muller(float m, float s) From 0d59e63be661075d279f5712efed353c94311275 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 13:49:31 +0200 Subject: [PATCH 46/81] replaced static array for NGSI mapping by dynamic one --- server/include/ngsi.h | 5 +---- server/src/ngsi.c | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/server/include/ngsi.h b/server/include/ngsi.h index dd64cc0c0..85ff870d5 100644 --- a/server/include/ngsi.h +++ b/server/include/ngsi.h @@ -47,15 +47,12 @@ struct ngsi { } structure; /**< Structure of published entitites */ - /** A mapping between indices of the S2SS messages and the attributes in ngsi::context */ - json_t **context_map; - /** The number of mappings in ngsi::context_map */ - int context_len; struct curl_slist *headers; /**< List of HTTP request headers for libcurl */ CURL *curl; /**< libcurl: handle */ json_t *context; /**< The complete JSON tree which will be used for contextUpdate requests */ + struct list mapping; /**< A mapping between indices of the S2SS messages and the attributes in ngsi::context */ }; /** Initialize global NGSI settings and maps shared memory regions. diff --git a/server/src/ngsi.c b/server/src/ngsi.c index db0445b69..b12e7a7fd 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -131,12 +131,12 @@ void ngsi_prepare_context(struct node *n, config_setting_t *mapping) struct ngsi *i = n->ngsi; i->context = json_object(); - i->context_len = config_setting_length(mapping); - i->context_map = alloc(i->context_len * sizeof(json_t *)); + + list_init(&i->mapping, NULL); json_t *elements = json_array(); - for (int j = 0; j < i->context_len; j++) { + for (int j = 0; j < config_setting_length(mapping); j++) { /* Get token */ config_setting_t *ctoken = config_setting_get_elem(mapping, j); const char *stoken = config_setting_get_string(ctoken); @@ -213,11 +213,13 @@ void ngsi_prepare_context(struct node *n, config_setting_t *mapping) )); } - json_t *attribute = i->context_map[j] = json_pack("{ s: s, s: s, s: s }", + json_t *attribute = json_pack("{ s: s, s: s, s: [ ] }", "name", aname, "type", atype, - "value", "0" + "value" ); + + list_push(&i->mapping, attribute); json_object_set_new(attribute, "metadatas", metadatas); json_array_append_new(attributes, attribute); @@ -341,8 +343,9 @@ int ngsi_write(struct node *n, struct msg *pool, int poolsize, int first, int cn error("NGSI nodes only can send a single message at once"); /* Update context */ - for (int j = 0; j < MIN(i->context_len, m->length); j++) { - json_t *attribute = i->context_map[j]; + for (int j = 0; j < MIN(i->mapping.length, m->length); j++) { + json_t *attribute = list_at(&i->mapping, j); + json_t *values = json_object_get(attribute, "value"); json_t *metadatas = json_object_get(attribute, "metadatas"); /* Update timestamp */ @@ -350,11 +353,8 @@ int ngsi_write(struct node *n, struct msg *pool, int poolsize, int first, int cn json_object_set(metadata_ts, "value", json_date(&MSG_TS(m))); /* Update value */ - char new[64]; - snprintf(new, sizeof(new), "%f", m->data[j].f); /** @todo for now we only support floating point values */ - - json_t *value = json_object_get(attribute, "value"); - json_string_set(value, new); + json_array_clear(values); + json_array_append_new(values, json_real(m->data[j].f)); } /* Update UUIDs for children structure */ From d0dcc8c49995c077530224d99976d2fbdaef2880 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 13:49:58 +0200 Subject: [PATCH 47/81] more useful names for Matlab histogram export --- server/src/hist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/hist.c b/server/src/hist.c index b41b3bd8b..f0856d2f7 100644 --- a/server/src/hist.c +++ b/server/src/hist.c @@ -167,7 +167,7 @@ void hist_matlab(struct hist *h, FILE *f) fprintf(f, "%lu = struct( ", time(NULL)); fprintf(f, "'min', %f, 'max', %f, ", h->low, h->high); - fprintf(f, "'ok', %u, too_high', %u, 'too_low', %u, ", h->total, h->higher, h->lower); + fprintf(f, "'total', %u, higher', %u, 'lower', %u, ", h->total, h->higher, h->lower); fprintf(f, "'highest', %f, 'lowest', %f, ", h->highest, h->lowest); fprintf(f, "'mean', %f, ", hist_mean(h)); fprintf(f, "'var', %f, ", hist_var(h)); From 19fbd94e6f927f9998d16b6f0b480e9335800638 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 15:29:26 +0200 Subject: [PATCH 48/81] added 'combine' aka vector support to NGSI node --- documentation/clients/NGSI.md | 34 +++++-- server/etc/example.conf | 17 ++-- server/include/ngsi.h | 13 +-- server/src/ngsi.c | 178 ++++++++++++++-------------------- 4 files changed, 108 insertions(+), 134 deletions(-) diff --git a/documentation/clients/NGSI.md b/documentation/clients/NGSI.md index a08d2f512..6e1ac9ddb 100644 --- a/documentation/clients/NGSI.md +++ b/documentation/clients/NGSI.md @@ -8,28 +8,44 @@ It's using `libcurl` and `libjansson` to communicate with the context broker ove ## Configuration +You can use the `combine` setting to send multiple samples in a vector. + Every `ngsi` node supports the following special settings: #### `endpoint` *(string: URL)* +#### `entity_id` *(string)* + +#### `entity_type` *(string)* + #### `ssl_verify` *(boolean)* #### `timeout` *(float: seconds)* -#### `structure` *("flat" | "children")* - - - `flat`: - - `children`: - #### `mapping` *(array of strings)* -Format for `structure = flat`: `"entityId(entityType).attributeName(attributeType)"` - -Format for `structure = children`: `"parentId(entityType).value(attributeType)"` +Format `AttributeName(AttributeType)` ### Example -@todo add example from example.conf + ngsi_node = { + type = "ngsi", + + ### The following settings are specific to the ngsi node-type!! ### + + endpoint = "http://46.101.131.212:1026",# The HTTP REST API endpoint of the FIRWARE context broker + + entity_id = "S3_ElectricalGrid", + entity_type = "ElectricalGridMonitoring", + + timeout = 5, # Timeout of HTTP request in seconds (default is 1) + verify_ssl = false, # Verification of SSL server certificates (default is true) + + mapping = [ # Format: "AttributeName(AttributeType)" + PTotalLosses(MW)", + "QTotalLosses(Mvar)" + ] + } ## Further reading diff --git a/server/etc/example.conf b/server/etc/example.conf index e178c772f..8cd0e679e 100644 --- a/server/etc/example.conf +++ b/server/etc/example.conf @@ -118,20 +118,17 @@ nodes = { ### The following settings are specific to the ngsi node-type!! ### endpoint = "http://46.101.131.212:1026",# The HTTP REST API endpoint of the FIRWARE context broker + + entity_id = "S3_ElectricalGrid", + entity_type = "ElectricalGridMonitoring", + timeout = 5, # Timeout of HTTP request in seconds (default is 1) - structure = "children", # Structure of published context (default is "flat") verify_ssl = false, # Verification of SSL server certificates (default is true) - mapping = [ # Example mapping for structure = flat - "rtds_sub1(V2).v2(float)", # Format: "entityId(entityType).AttributeName(AttributeType)" - "rtds_sub2(power).p1(float)" + mapping = [ # Format: "AttributeName(AttributeType)" + PTotalLosses(MW)", + "QTotalLosses(Mvar)" ] - # mapping = [ # Alternative mapping for structure = children - # "fa846ed3-5871-11e5-b0cd-ecf4bb16fe0c(GridSectionDataValue).value(float)", # Index #0 - # "1d2c63e4-6130-11e5-9b0d-001c42f23160(GridSectionDataValue).value(float)" # Index #1 - # .... # Index #n - # (every line correspondents to one value) - #] } }; diff --git a/server/include/ngsi.h b/server/include/ngsi.h index 85ff870d5..4350f698d 100644 --- a/server/include/ngsi.h +++ b/server/include/ngsi.h @@ -34,18 +34,13 @@ struct node; struct ngsi { const char *endpoint; /**< The NGSI context broker endpoint URL. */ - const char *token; /**< An optional authentication token which will be sent as HTTP header. */ + const char *entity_id; /**< The context broker entity id related to this node */ + const char *entity_type; /**< The type of the entity */ + const char *access_token; /**< An optional authentication token which will be sent as HTTP header. */ double timeout; /**< HTTP timeout in seconds */ - int ssl_verify; /**< Boolean flag whether SSL server certificates should be verified or not. */ - - - enum ngsi_structure { - NGSI_FLAT, - NGSI_CHILDREN - } structure; /**< Structure of published entitites */ - + int ssl_verify; /**< Boolean flag whether SSL server certificates should be verified or not. */ struct curl_slist *headers; /**< List of HTTP request headers for libcurl */ diff --git a/server/src/ngsi.c b/server/src/ngsi.c index b12e7a7fd..4dd7b9e23 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -107,7 +107,7 @@ static int ngsi_request(CURL *handle, const char *endpoint, const char *operatio curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &time); curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &code); - debug(20, "Request to context broker completed in %.4f seconds", time); + debug(10, "Request to context broker completed in %.4f seconds", time); debug(20, "Response from context broker (code=%ld):\n%s", code, chunk.data); json_error_t err; @@ -129,53 +129,44 @@ static int ngsi_request(CURL *handle, const char *endpoint, const char *operatio void ngsi_prepare_context(struct node *n, config_setting_t *mapping) { struct ngsi *i = n->ngsi; - - i->context = json_object(); - + list_init(&i->mapping, NULL); + i->context = json_object(); + json_t *elements = json_array(); + json_object_set_new(i->context, "contextElements", elements); + + json_t *entity = json_pack("{ s: s, s: s, s: b }", + "id", i->entity_id, + "type", i->entity_type, + "isPattern", 0 + ); + json_array_append_new(elements, entity); + + json_t *attributes = json_array(); + json_object_set_new(entity, "attributes", attributes); for (int j = 0; j < config_setting_length(mapping); j++) { - /* Get token */ - config_setting_t *ctoken = config_setting_get_elem(mapping, j); - const char *stoken = config_setting_get_string(ctoken); - if (!stoken) - cerror(mapping, "Invalid token"); + const char *token = config_setting_get_string_elem(mapping, j); + if (!token) + cerror(mapping, "Invalid mapping token"); /* Parse token */ - char eid[64], etype[64], aname[64], atype[64]; - if (sscanf(stoken, "%63[^().](%63[^().]).%63[^().](%63[^().])", eid, etype, aname, atype) != 4) - cerror(ctoken, "Invalid token: '%s'", stoken); - - /* Create entity */ - json_t *attributes; /* borrowed reference */ - - json_t *entity = json_lookup(elements, "id", eid); - if (!entity) { - entity = json_pack("{ s: s, s: s, s: b }", - "id", eid, - "type", etype, - "isPattern", 0 - ); - json_array_append_new(elements, entity); - - attributes = json_array(); - json_object_set_new(entity, "attributes", attributes); - } - else { - if (i->structure == NGSI_CHILDREN) - cerror(ctoken, "Duplicate mapping for index %u", j); - - attributes = json_object_get(entity, "attributes"); - } - - /* Create attribute for entity */ - if (json_lookup(attributes, "name", aname)) - cerror(ctoken, "Duplicated attribute '%s' in NGSI mapping of node '%s'", aname, n->name); + char aname[64], atype[64]; + if (sscanf(token, "%[^(](%[^)])", aname, atype) != 2) + cerror(mapping, "Invalid mapping token: '%s'", token); + json_t *attribute = json_pack("{ s: s, s: s, s: [ ] }", + "name", aname, + "type", atype, + "value" + ); + json_array_append_new(attributes, attribute); + /* Create Metadata for attribute */ json_t *metadatas = json_array(); + json_object_set_new(attribute, "metadatas", metadatas); json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: s+ }", "name", "source", @@ -191,41 +182,10 @@ void ngsi_prepare_context(struct node *n, config_setting_t *mapping) "name", "timestamp", "type", "date", "value", "" - )); - - if (i->structure == NGSI_CHILDREN) { - json_array_append_new(attributes, json_pack("{ s: s, s: s, s: s }", - "name", "parentId", - "type", "uuid", - "value", eid - )); - - json_array_append_new(attributes, json_pack("{ s: s, s: s, s: s }", - "name", "source", - "type", "string", - "value", "measurement" - )); - - json_array_append_new(attributes, json_pack("{ s: s, s: s, s: o }", - "name", "timestamp", - "type", "date", - "value", json_date(NULL) - )); - } - - json_t *attribute = json_pack("{ s: s, s: s, s: [ ] }", - "name", aname, - "type", atype, - "value" - ); + )); list_push(&i->mapping, attribute); - - json_object_set_new(attribute, "metadatas", metadatas); - json_array_append_new(attributes, attribute); } - - json_object_set_new(i->context, "contextElements", elements); } int ngsi_init(int argc, char *argv[], struct settings *set) @@ -244,11 +204,17 @@ int ngsi_parse(config_setting_t *cfg, struct node *n) { struct ngsi *i = alloc(sizeof(struct ngsi)); - if (!config_setting_lookup_string(cfg, "token", &i->token)) - i->token = NULL; /* disabled by default */ + if (!config_setting_lookup_string(cfg, "access_token", &i->access_token)) + i->access_token = NULL; /* disabled by default */ if (!config_setting_lookup_string(cfg, "endpoint", &i->endpoint)) cerror(cfg, "Missing NGSI endpoint for node '%s'", n->name); + + if (!config_setting_lookup_string(cfg, "entity_id", &i->entity_id)) + cerror(cfg, "Missing NGSI entity ID for node '%s'", n->name); + + if (!config_setting_lookup_string(cfg, "entity_type", &i->entity_type)) + cerror(cfg, "Missing NGSI entity type for node '%s'", n->name); if (!config_setting_lookup_bool(cfg, "ssl_verify", &i->ssl_verify)) i->ssl_verify = 1; /* verify by default */ @@ -256,18 +222,6 @@ int ngsi_parse(config_setting_t *cfg, struct node *n) if (!config_setting_lookup_float(cfg, "timeout", &i->timeout)) i->timeout = 1; /* default value */ - const char *structure; - if (!config_setting_lookup_string(cfg, "structure", &structure)) - i->structure = NGSI_FLAT; - else { - if (!strcmp(structure, "flat")) - i->structure = NGSI_FLAT; - else if (!strcmp(structure, "children")) - i->structure = NGSI_CHILDREN; - else - cerror(cfg, "Invalid NGSI entity structure '%s' for node '%s'", structure, n->name); - } - n->ngsi = i; config_setting_t *mapping = config_setting_get_member(cfg, "mapping"); @@ -296,8 +250,8 @@ int ngsi_open(struct node *n) i->curl = curl_easy_init(); i->headers = NULL; - if (i->token) { - snprintf(buf, sizeof(buf), "Auth-Token: %s", i->token); + if (i->access_token) { + snprintf(buf, sizeof(buf), "Auth-Token: %s", i->access_token); i->headers = curl_slist_append(i->headers, buf); } @@ -330,44 +284,56 @@ int ngsi_close(struct node *n) } int ngsi_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt) -{ +{ +/* struct ngsi *i = n->ngsi; + struct msg *m = &pool[first % poolsize]; + int ret; + + json_t *response; + json_t *entities = json_array(); + json_t *query = json_pack("{ s: o }", "entities", entities); + + ret = ngsi_request(i->curl, i->endpoint, "queryContext", NULL, NULL); + if (ret < 0) { + warn("Failed to query data from context broker"); + return 0; + } +*/ return -1; /** @todo not yet implemented */ } int ngsi_write(struct node *n, struct msg *pool, int poolsize, int first, int cnt) { struct ngsi *i = n->ngsi; - struct msg *m = &pool[first % poolsize]; - - if (cnt > 1) - error("NGSI nodes only can send a single message at once"); + + /* First message */ + struct msg *fm = &pool[first % poolsize]; /* Update context */ - for (int j = 0; j < MIN(i->mapping.length, m->length); j++) { + for (int j = 0; j < MIN(i->mapping.length, fm->length); j++) { json_t *attribute = list_at(&i->mapping, j); json_t *values = json_object_get(attribute, "value"); json_t *metadatas = json_object_get(attribute, "metadatas"); /* Update timestamp */ json_t *metadata_ts = json_lookup(metadatas, "name", "timestamp"); - json_object_set(metadata_ts, "value", json_date(&MSG_TS(m))); + json_object_set(metadata_ts, "value", json_date(&MSG_TS(fm))); /* Update value */ json_array_clear(values); - json_array_append_new(values, json_real(m->data[j].f)); + for (int k = 0; k < cnt; k++) { + struct msg *m = &pool[(first + k) % poolsize]; + + double tsms = (double) m->ts.sec * 1e3 + m->ts.nsec / 1e6; + + json_array_append_new(values, json_pack("[ o, o ]", + json_real(tsms), + json_real(m->data[j].f) + )); + } } - - /* Update UUIDs for children structure */ - if (i->structure == NGSI_CHILDREN) { - json_t *entity, *elements = json_object_get(i->context, "contextElements"); - size_t ind; - json_array_foreach(elements, ind, entity) - json_object_set_new(entity, "id", json_uuid()); - - json_object_set_new(i->context, "updateAction", json_string("APPEND")); - } - else - json_object_set_new(i->context, "updateAction", json_string("UPDATE")); + + json_object_set_new(i->context, "updateAction", json_string("UPDATE")); // @todo REPLACE? json_t *response; int code = ngsi_request(i->curl, i->endpoint, "updateContext", i->context, &response); From 24435040e147a4df190ae42aa2f1b1f5044bc5b3 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 15:29:47 +0200 Subject: [PATCH 49/81] added missing example configurations to other node types --- documentation/clients/GTFPGA.md | 10 +++++++++- documentation/clients/OPAL.md | 12 ++++++++++++ documentation/clients/Socket.md | 18 +++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/documentation/clients/GTFPGA.md b/documentation/clients/GTFPGA.md index 1a5db6722..9eda5944c 100644 --- a/documentation/clients/GTFPGA.md +++ b/documentation/clients/GTFPGA.md @@ -20,5 +20,13 @@ Every `gtfpga` node support the following special settings: ### Example -@todo Add excerpt from example.conf + gtfpga_node = { + type = "gtfpga", + + ### The following settings are specific to the gtfpga node-type!! ### + slot = "01:00.0", # The PCIe slot location (see first column in 'lspci' output) + id = "1ab8:4005", # The PCIe vendor:device ID (see third column in 'lspci -n' output) + + rate = 1 + } diff --git a/documentation/clients/OPAL.md b/documentation/clients/OPAL.md index e115f38f5..692019ad9 100644 --- a/documentation/clients/OPAL.md +++ b/documentation/clients/OPAL.md @@ -15,6 +15,18 @@ Every `opal` node supports the following special settings: #### `reply` *(boolean)* +### Example + + opal_node = { # The server can be started as an Asynchronous process + type = "opal", # from within an OPAL-RT model. + + ### The following settings are specific to the opal node-type!! ### + + send_id = 1, # It's possible to have multiple send / recv Icons per model + recv_id = 1, # Specify the ID here. + reply = true + } + ## Arguments for OPAL-RT block RT-LAB already provides a block to establish simple TCP/IP communication: ??? diff --git a/documentation/clients/Socket.md b/documentation/clients/Socket.md index 2748cb0e8..4d7817a72 100644 --- a/documentation/clients/Socket.md +++ b/documentation/clients/Socket.md @@ -27,7 +27,23 @@ Every `socket` node supports the following special settings: ### Example -@todo Add excerpt from example.conf + udp_node = { # The dictionary is indexed by the name of the node. + type = "socket", # Type can be one of: socket, opal, file, gtfpga, ngsi + # Start the server without arguments for a list of supported node types. + + ### The following settings are specific to the socket node-type!! ### + + layer = "udp" # Layer can be one of: + # udp Send / recv UDP packets + # ip Send / recv IP packets + # eth Send / recv raw Ethernet frames (IEEE802.3) + + + local = "127.0.0.1:12001", # This node only received messages on this IP:Port pair + remote = "127.0.0.1:12000" # This node sents outgoing messages to this IP:Port pair + + combine = 30 # Receive and sent 30 samples per message (multiplexing). + } ## Packet Format From 6460108c9252c60e316e5a48ec1c5c4cfa235817 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 16:16:25 +0200 Subject: [PATCH 50/81] Improved periodic statistics --- server/include/config.h | 2 +- server/include/hooks.h | 3 +++ server/include/log.h | 1 + server/src/hooks.c | 36 ++++++++++++++++++++++++++++++++---- server/src/ngsi.c | 4 ++-- server/src/path.c | 7 ++++--- server/src/server.c | 4 +--- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/server/include/config.h b/server/include/config.h index 0eac018c1..d12069d83 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -27,7 +27,7 @@ #define DEFAULT_POOLSIZE 32 /** Width of log output in characters */ -#define LOG_WIDTH 74 +#define LOG_WIDTH 132 /** Socket priority */ #define SOCKET_PRIO 7 diff --git a/server/include/hooks.h b/server/include/hooks.h index 17e3c503e..977f551d6 100644 --- a/server/include/hooks.h +++ b/server/include/hooks.h @@ -132,4 +132,7 @@ int hook_stats(struct path *p, struct hook *h, int when); /** Core hook: send path statistics to another node */ int hook_stats_send(struct path *p, struct hook *h, int when); +/** Not a hook: just prints header for periodic statistics */ +void hook_stats_header(); + #endif /** _HOOKS_H_ @} @} */ diff --git a/server/include/log.h b/server/include/log.h index a16a95d92..375c3d394 100644 --- a/server/include/log.h +++ b/server/include/log.h @@ -24,6 +24,7 @@ #define INFO "" #define WARN YEL("Warn ") #define ERROR RED("Error") +#define STATS MAG("Stats") /** Change log indention for current thread. * diff --git a/server/src/hooks.c b/server/src/hooks.c index 959f17740..b656eef25 100644 --- a/server/src/hooks.c +++ b/server/src/hooks.c @@ -347,9 +347,9 @@ int hook_stats(struct path *p, struct hook *h, int when) } case HOOK_PATH_STOP: - if (p->hist_delay.total) { info("One-way delay (received):"); hist_print(&p->hist_delay); } - if (p->hist_gap.total) { info("Message gap time:"); hist_print(&p->hist_gap); } - if (p->hist_sequence.total) { info("Sequence number gaps:"); hist_print(&p->hist_sequence); } + if (p->hist_delay.total) { info("One-way delay (received):"); hist_print(&p->hist_delay); } + if (p->hist_gap.total) { info("Message gap time (received):"); hist_print(&p->hist_gap); } + if (p->hist_sequence.total) { info("Sequence number gaps (received):"); hist_print(&p->hist_sequence); } break; case HOOK_PATH_RESTART: @@ -360,7 +360,17 @@ int hook_stats(struct path *p, struct hook *h, int when) case HOOK_PERIODIC: { char *buf = path_print(p); - info("%-32s : %-8u %-8u %-8u %-8u %-8u %-8u", buf, p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun); + + if (p->received > 0) + log_print(STATS, "%-40s|%10.2g|%10.2f|%10u|%10u|%10u|%10u|%10u|%10u|%10u|", buf, + hist_mean(&p->hist_delay), 1 / hist_mean(&p->hist_gap), + p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun, p->current->length + ); + else + log_print(STATS, "%-40s|%10s|%10s|%10u|%10u|%10u|%10u|%10u|%10u|%10s|", buf, "", "", + p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun, "" + ); + free(buf); break; } @@ -369,6 +379,24 @@ int hook_stats(struct path *p, struct hook *h, int when) return 0; } +void hook_stats_header() +{ +#define UNIT(u) "(" YEL(u) ")" + + log_print(STATS, "%-40s|%19s|%19s|%19s|%19s|%19s|%19s|%19s|%10s|%10s|", "Source " MAG("=>") " Destination", + "OWD" UNIT("S") " ", + "Rate" UNIT("p/S") " ", + "Sent" UNIT("p") " ", + "Recv" UNIT("p") " ", + "Drop" UNIT("p") " ", + "Skip" UNIT("p") " ", + "Inval" UNIT("p") " ", + "Overuns ", + "Values " + ); + line(); +} + REGISTER_HOOK("stats_send", 99, hook_stats_send, HOOK_PRIVATE | HOOK_MSG) int hook_stats_send(struct path *p, struct hook *h, int when) { diff --git a/server/src/ngsi.c b/server/src/ngsi.c index 4dd7b9e23..9474d0736 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -107,8 +107,8 @@ static int ngsi_request(CURL *handle, const char *endpoint, const char *operatio curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &time); curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &code); - debug(10, "Request to context broker completed in %.4f seconds", time); - debug(20, "Response from context broker (code=%ld):\n%s", code, chunk.data); + debug(16, "Request to context broker completed in %.4f seconds", time); + debug(17, "Response from context broker (code=%ld):\n%s", code, chunk.data); json_error_t err; json_t *resp = json_loads(chunk.data, 0, &err); diff --git a/server/src/path.c b/server/src/path.c index 38872fecf..534d325c1 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -32,7 +32,7 @@ static void path_write(struct path *p) n->combine /* Number of messages which should be sent */ ); - debug(1, "Sent %u messages to node '%s'", sent, n->name); + debug(15, "Sent %u messages to node '%s'", sent, n->name); p->sent += sent; clock_gettime(CLOCK_REALTIME, &p->ts_sent); @@ -95,7 +95,7 @@ static void * path_run(void *arg) /** @todo Replace this timestamp by hardware timestamping */ clock_gettime(CLOCK_REALTIME, &p->ts_recv); - debug(10, "Received %u messages from node '%s'", recv, p->in->name); + debug(15, "Received %u messages from node '%s'", recv, p->in->name); /* Run preprocessing hooks */ if (path_run_hook(p, HOOK_PRE)) { @@ -134,7 +134,8 @@ static void * path_run(void *arg) int path_start(struct path *p) { INDENT char *buf = path_print(p); - info("Starting path: %s (poolsize = %u, msgsize = %u, #hooks = %zu)", buf, p->poolsize, p->msgsize, list_length(&p->hooks)); + info("Starting path: %s (poolsize = %u, msgsize = %u, #hooks = %zu, rate = %.1f)", + buf, p->poolsize, p->msgsize, list_length(&p->hooks), p->rate); free(buf); /* We sort the hooks according to their priority before starting the path */ diff --git a/server/src/server.c b/server/src/server.c index a13a25e50..2e2d6f0af 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -189,9 +189,7 @@ int main(int argc, char *argv[]) /* Run! */ if (settings.stats > 0) { - info("%-32s : %-8s %-8s %-8s %-8s %-8s %-8s", - "Source " MAG("=>") " Destination", "#Sent", "#Recv", "#Drop", "#Skip", "#Invalid", "#Overuns"); - line(); + hook_stats_header(); do list_foreach(struct path *p, &paths) { usleep(settings.stats * 1e6); From 51db785bfd89e2af82cd3cd6dcb5ad515a3b5493 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 16:17:51 +0200 Subject: [PATCH 51/81] reordered a function (no changes) --- server/src/cfg.c | 67 ++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/server/src/cfg.c b/server/src/cfg.c index 84d0785c1..e2b9dda4c 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -243,6 +243,39 @@ int config_parse_nodelist(config_setting_t *cfg, struct list *list, struct list return 0; } +int config_parse_node(config_setting_t *cfg, struct list *nodes, struct settings *set) +{ + const char *type; + int ret; + + struct node *n = node_create(); + + /* Required settings */ + n->cfg = cfg; + n->name = config_setting_name(cfg); + if (!n->name) + cerror(cfg, "Missing node name"); + + if (!config_setting_lookup_string(cfg, "type", &type)) + cerror(cfg, "Missing node type"); + + if (!config_setting_lookup_int(cfg, "combine", &n->combine)) + n->combine = 1; + + if (!config_setting_lookup_int(cfg, "affinity", &n->combine)) + n->affinity = set->affinity; + + n->vt = list_lookup(&node_types, type); + if (!n->vt) + cerror(cfg, "Invalid type for node '%s'", n->name); + + ret = n->vt->parse(cfg, n); + if (!ret) + list_push(nodes, n); + + return ret; +} + int config_parse_hooklist(config_setting_t *cfg, struct list *list) { switch (config_setting_type(cfg)) { case CONFIG_TYPE_STRING: @@ -285,37 +318,3 @@ int config_parse_hook(config_setting_t *cfg, struct list *list) return 0; } - -int config_parse_node(config_setting_t *cfg, struct list *nodes, struct settings *set) -{ - const char *type; - int ret; - - struct node *n = node_create(); - - /* Required settings */ - n->cfg = cfg; - n->name = config_setting_name(cfg); - if (!n->name) - cerror(cfg, "Missing node name"); - - if (!config_setting_lookup_string(cfg, "type", &type)) - cerror(cfg, "Missing node type"); - - if (!config_setting_lookup_int(cfg, "combine", &n->combine)) - n->combine = 1; - - if (!config_setting_lookup_int(cfg, "affinity", &n->combine)) - n->affinity = set->affinity; - - n->vt = list_lookup(&node_types, type); - if (!n->vt) - cerror(cfg, "Invalid type for node '%s'", n->name); - - ret = n->vt->parse(cfg, n); - if (!ret) - list_push(nodes, n); - - return ret; -} - From 24fa12c36957b8961d260e749f484f4e6f630c9b Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 16:25:08 +0200 Subject: [PATCH 52/81] replace clock_gettime() by time_now() --- server/src/hooks.c | 2 +- server/src/log.c | 5 ++--- server/src/path.c | 6 +++--- server/src/random.c | 3 +-- server/src/receive.c | 3 +-- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/server/src/hooks.c b/server/src/hooks.c index b656eef25..4e7d9075b 100644 --- a/server/src/hooks.c +++ b/server/src/hooks.c @@ -254,7 +254,7 @@ int hook_skip_first(struct path *p, struct hook *h, int when) break; case HOOK_PATH_START: - clock_gettime(CLOCK_REALTIME, &private->started); + private->started = time_now(); break; case HOOK_POST: { diff --git a/server/src/log.c b/server/src/log.c index 984385cb5..f99a33e1e 100644 --- a/server/src/log.c +++ b/server/src/log.c @@ -56,7 +56,7 @@ void log_setlevel(int lvl) void log_init() { - clock_gettime(CLOCK_REALTIME, &epoch); + epoch = time_now(); debug(10, "Debug clock resetted"); } @@ -71,11 +71,10 @@ void log_print(const char *lvl, const char *fmt, ...) void log_vprint(const char *lvl, const char *fmt, va_list ap) { - struct timespec ts; + struct timespec ts = time_now(); char *buf = alloc(512); /* Timestamp */ - clock_gettime(CLOCK_REALTIME, &ts); strcatf(&buf, "%10.3f ", time_delta(&epoch, &ts)); /* Severity */ diff --git a/server/src/path.c b/server/src/path.c index 534d325c1..f68c5d9dc 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -35,7 +35,7 @@ static void path_write(struct path *p) debug(15, "Sent %u messages to node '%s'", sent, n->name); p->sent += sent; - clock_gettime(CLOCK_REALTIME, &p->ts_sent); + p->ts_sent = time_now(); /** @todo use hardware timestamps for socket node type */ } } @@ -93,8 +93,8 @@ static void * path_run(void *arg) continue; /** @todo Replace this timestamp by hardware timestamping */ - clock_gettime(CLOCK_REALTIME, &p->ts_recv); - + p->ts_recv = time_now(); + debug(15, "Received %u messages from node '%s'", recv, p->in->name); /* Run preprocessing hooks */ diff --git a/server/src/random.c b/server/src/random.c index c253592bc..4e9da6238 100644 --- a/server/src/random.c +++ b/server/src/random.c @@ -59,8 +59,7 @@ int main(int argc, char *argv[]) while (limit-- > 0 || argc < 4) { m.sequence += timerfd_wait(tfd); - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); + struct timespec ts = time_now(); m.ts.sec = ts.tv_sec; m.ts.nsec = ts.tv_nsec; diff --git a/server/src/receive.c b/server/src/receive.c index d11a9716b..7e99756c2 100644 --- a/server/src/receive.c +++ b/server/src/receive.c @@ -113,8 +113,7 @@ int main(int argc, char *argv[]) fprintf(stderr, "# %-20s\t\t%s\n", "sec.nsec+offset(seq)", "data[]"); for (;;) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); + struct timespec ts = time_now(); int recv = node_read(node, pool, node->combine, 0, node->combine); for (int i = 0; i < recv; i++) { From 504e64a7c8badccc14cdb3e99f1e397924ceef5d Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 16:59:16 +0200 Subject: [PATCH 53/81] fixed statistics collections --- server/include/hooks.h | 2 +- server/include/path.h | 16 ++++++----- server/src/hooks.c | 60 ++++++++++++++++++++++++++---------------- server/src/path.c | 1 + 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/server/include/hooks.h b/server/include/hooks.h index 977f551d6..13b366f8a 100644 --- a/server/include/hooks.h +++ b/server/include/hooks.h @@ -72,7 +72,7 @@ enum hook_type { /** All path related actions */ HOOK_PATH = HOOK_PATH_START | HOOK_PATH_STOP | HOOK_PATH_RESTART, /** Hooks which are used to collect statistics. */ - HOOK_STATS = HOOK_INTERNAL | HOOK_PRIVATE | HOOK_PATH | HOOK_MSG | HOOK_PERIODIC, + HOOK_STATS = HOOK_INTERNAL | HOOK_PRIVATE | HOOK_PATH | HOOK_MSG | HOOK_PRE | HOOK_PERIODIC, /** All hooks */ HOOK_ALL = HOOK_INTERNAL - 1 /** @} */ diff --git a/server/include/path.h b/server/include/path.h index c381c3069..cf78a2e06 100644 --- a/server/include/path.h +++ b/server/include/path.h @@ -68,17 +68,21 @@ struct path /* The following fields are mostly managed by hook_ functions */ + /** Histogram for one-way-delay (OWD) of received messages */ + struct hist hist_owd; + /** Histogram for inter message timestamps (as sent by remote) */ + struct hist hist_gap_msg; + /** Histogram for inter message arrival time (as seen by this instance) */ + struct hist hist_gap_recv; /** Histogram of sequence number displacement of received messages */ - struct hist hist_sequence; - /** Histogram for delay of received messages */ - struct hist hist_delay; - /** Histogram for inter message delay (time between received messages) */ - struct hist hist_gap; - + struct hist hist_gap_seq; + /** Last message received */ struct timespec ts_recv; /** Last message sent */ struct timespec ts_sent; + /** Previous message received (old value of path::ts_recv) */ + struct timespec ts_last; /** Counter for sent messages to all outgoing nodes */ unsigned int sent; diff --git a/server/src/hooks.c b/server/src/hooks.c index 4e7d9075b..8d35e3ef9 100644 --- a/server/src/hooks.c +++ b/server/src/hooks.c @@ -322,40 +322,55 @@ int hook_stats(struct path *p, struct hook *h, int when) switch (when) { case HOOK_INIT: /** @todo Allow configurable bounds for histograms */ - hist_create(&p->hist_sequence, -HIST_SEQ, +HIST_SEQ, 1); - hist_create(&p->hist_delay, 0, 2, 100e-3); - hist_create(&p->hist_gap, 0, 40e-3, 1e-3); + hist_create(&p->hist_gap_seq, -HIST_SEQ, +HIST_SEQ, 1); + hist_create(&p->hist_owd, 0, 1, 100e-3); + hist_create(&p->hist_gap_msg, 90e-3, 110e-3, 1e-3); + hist_create(&p->hist_gap_recv, 90e-3, 110e-3, 1e-3); break; case HOOK_DEINIT: - hist_destroy(&p->hist_sequence); - hist_destroy(&p->hist_delay); - hist_destroy(&p->hist_gap); + hist_destroy(&p->hist_gap_seq); + hist_destroy(&p->hist_owd); + hist_destroy(&p->hist_gap_msg); + hist_destroy(&p->hist_gap_recv); break; + + case HOOK_PRE: + /* Exclude first message from statistics */ + if (p->received > 0) { + double gap = time_delta(&p->ts_last, &p->ts_recv); + + hist_put(&p->hist_gap_recv, gap); + } case HOOK_MSG: { struct msg *prev = p->previous, *cur = p->current; - - int dist = cur->sequence - (int32_t) prev->sequence; - double delay = time_delta(&p->ts_recv, &MSG_TS(cur)); - double gap = time_delta(&MSG_TS(prev), &MSG_TS(cur)); - hist_put(&p->hist_sequence, dist); - hist_put(&p->hist_delay, delay); - hist_put(&p->hist_gap, gap); + /* Exclude first message from statistics */ + if (p->received > 0) { + int dist = cur->sequence - (int32_t) prev->sequence; + double delay = time_delta(&MSG_TS(cur), &p->ts_recv); + double gap = time_delta(&MSG_TS(prev), &MSG_TS(cur)); + + hist_put(&p->hist_gap_msg, gap); + hist_put(&p->hist_gap_seq, dist); + hist_put(&p->hist_owd, delay); + } break; } case HOOK_PATH_STOP: - if (p->hist_delay.total) { info("One-way delay (received):"); hist_print(&p->hist_delay); } - if (p->hist_gap.total) { info("Message gap time (received):"); hist_print(&p->hist_gap); } - if (p->hist_sequence.total) { info("Sequence number gaps (received):"); hist_print(&p->hist_sequence); } + if (p->hist_owd.total) { info("One-way delay:"); hist_print(&p->hist_owd); } + if (p->hist_gap_recv.total){ info("Inter-message arrival time:"); hist_print(&p->hist_gap_recv); } + if (p->hist_gap_msg.total) { info("Inter-message ts gap:"); hist_print(&p->hist_gap_msg); } + if (p->hist_gap_seq.total) { info("Inter-message sequence number gaps:"); hist_print(&p->hist_gap_seq); } break; case HOOK_PATH_RESTART: - hist_reset(&p->hist_sequence); - hist_reset(&p->hist_delay); - hist_reset(&p->hist_gap); + hist_reset(&p->hist_owd); + hist_reset(&p->hist_gap_seq); + hist_reset(&p->hist_gap_msg); + hist_reset(&p->hist_gap_recv); break; case HOOK_PERIODIC: { @@ -363,7 +378,7 @@ int hook_stats(struct path *p, struct hook *h, int when) if (p->received > 0) log_print(STATS, "%-40s|%10.2g|%10.2f|%10u|%10u|%10u|%10u|%10u|%10u|%10u|", buf, - hist_mean(&p->hist_delay), 1 / hist_mean(&p->hist_gap), + p->hist_owd.last, 1 / p->hist_gap_msg.last, p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun, p->current->length ); else @@ -429,8 +444,9 @@ int hook_stats_send(struct path *p, struct hook *h, int when) m.data[m.length++].f = p->invalid; m.data[m.length++].f = p->skipped; m.data[m.length++].f = p->dropped; - m.data[m.length++].f = p->hist_delay.last, - m.data[m.length++].f = p->hist_gap.last; + m.data[m.length++].f = p->hist_owd.last, + m.data[m.length++].f = p->hist_gap_msg.last; + m.data[m.length++].f = p->hist_gap_recv.last; /* Send single message with statistics to destination node */ node_write_single(private->dest, &m); diff --git a/server/src/path.c b/server/src/path.c index f68c5d9dc..e5de77570 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -93,6 +93,7 @@ static void * path_run(void *arg) continue; /** @todo Replace this timestamp by hardware timestamping */ + p->ts_last = p->ts_recv; p->ts_recv = time_now(); debug(15, "Received %u messages from node '%s'", recv, p->in->name); From d91b76c61ae76637e77fe467b624c8b0a84e92d1 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 17:29:51 +0200 Subject: [PATCH 54/81] decreasing verbosity of socket --- server/src/socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/socket.c b/server/src/socket.c index 3a7ad6a8c..fded7353b 100644 --- a/server/src/socket.c +++ b/server/src/socket.c @@ -204,7 +204,7 @@ int socket_read(struct node *n, struct msg *pool, int poolsize, int first, int c else if (bytes < 0) serror("Failed recv"); - debug(10, "Received packet of %u bytes: %u samples a %u values per sample", bytes, cnt, (bytes / cnt) / 4 - 4); + debug(17, "Received packet of %u bytes: %u samples a %u values per sample", bytes, cnt, (bytes / cnt) / 4 - 4); for (int i = 0; i < cnt; i++) { struct msg *m = &pool[(first+poolsize+i) % poolsize]; From 4dc0db5163208362b8edc9bcd16b900b8f8f7aaf Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 17:30:28 +0200 Subject: [PATCH 55/81] changed commenting style --- server/include/path.h | 92 ++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 58 deletions(-) diff --git a/server/include/path.h b/server/include/path.h index cf78a2e06..f0a873065 100644 --- a/server/include/path.h +++ b/server/include/path.h @@ -31,71 +31,47 @@ */ struct path { - /** Pointer to the incoming node */ - struct node *in; - /** Pointer to the first outgoing node. - * Usually this is only a pointer to the first list element of path::destinations. */ - struct node *out; + struct node *in; /**< Pointer to the incoming node */ + struct node *out; /**< Pointer to the first outgoing node ( path::out == list_first(path::destinations) */ - /** List of all outgoing nodes */ - struct list destinations; - /** List of function pointers to hooks */ - struct list hooks; + struct list destinations; /**< List of all outgoing nodes */ + struct list hooks; /**< List of function pointers to hooks */ - /** Timer file descriptor for fixed rate sending */ - int tfd; - /** Send messages with a fixed rate over this path */ - double rate; + int tfd; /**< Timer file descriptor for fixed rate sending */ + double rate; /**< Send messages with a fixed rate over this path */ - /** Maximum number of values per message for this path */ - int msgsize; - /** Size of the history buffer in number of messages */ - int poolsize; - /** A circular buffer of past messages */ - struct msg *pool; - - /** A pointer to the last received message */ - struct msg *current; - /** A pointer to the previously received message */ - struct msg *previous; - - /** The thread id for this path */ - pthread_t recv_tid; - /** A second thread id for fixed rate sending thread */ - pthread_t sent_tid; - /** A pointer to the libconfig object which instantiated this path */ - config_setting_t *cfg; + int msgsize; /**< Maximum number of values per message for this path */ + int poolsize; /**< Size of the history buffer in number of messages */ - /* The following fields are mostly managed by hook_ functions */ + struct msg *pool; /**< A circular buffer of past messages */ + + struct msg *current; /**< A pointer to the last received message */ + struct msg *previous; /**< A pointer to the previously received message */ + + pthread_t recv_tid; /**< The thread id for this path */ + pthread_t sent_tid; /**< A second thread id for fixed rate sending thread */ + + config_setting_t *cfg; /**< A pointer to the libconfig object which instantiated this path */ - /** Histogram for one-way-delay (OWD) of received messages */ - struct hist hist_owd; - /** Histogram for inter message timestamps (as sent by remote) */ - struct hist hist_gap_msg; - /** Histogram for inter message arrival time (as seen by this instance) */ - struct hist hist_gap_recv; - /** Histogram of sequence number displacement of received messages */ - struct hist hist_gap_seq; + /** The following fields are mostly managed by hook_ functions @{ */ + + struct hist hist_owd; /**< Histogram for one-way-delay (OWD) of received messages */ + struct hist hist_gap_msg; /**< Histogram for inter message timestamps (as sent by remote) */ + struct hist hist_gap_recv; /**< Histogram for inter message arrival time (as seen by this instance) */ + struct hist hist_gap_seq; /**< Histogram of sequence number displacement of received messages */ - /** Last message received */ - struct timespec ts_recv; - /** Last message sent */ - struct timespec ts_sent; - /** Previous message received (old value of path::ts_recv) */ - struct timespec ts_last; + struct timespec ts_recv; /**< Last message received */ + struct timespec ts_sent; /**< Last message sent */ + struct timespec ts_last; /**< Previous message received (old value of path::ts_recv) */ - /** Counter for sent messages to all outgoing nodes */ - unsigned int sent; - /** Counter for received messages from all incoming nodes */ - unsigned int received; - /** Counter for invalid messages */ - unsigned int invalid; - /** Counter for skipped messages due to hooks */ - unsigned int skipped; - /** Counter for dropped messages due to reordering */ - unsigned int dropped; - /** Counter of overruns for fixed-rate sending */ - unsigned int overrun; + unsigned int sent; /**< Counter for sent messages to all outgoing nodes */ + unsigned int received; /**< Counter for received messages from all incoming nodes */ + unsigned int invalid; /**< Counter for invalid messages */ + unsigned int skipped; /**< Counter for skipped messages due to hooks */ + unsigned int dropped; /**< Counter for dropped messages due to reordering */ + unsigned int overrun; /**< Counter of overruns for fixed-rate sending */ + + /** @} */ }; /** Create a path by allocating dynamic memory. */ From 405af5d0e229f97e5df68713d72d70834a626b14 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 18:47:52 +0200 Subject: [PATCH 56/81] made vtable pointer private (renamed from node::vt to node::_vt) --- server/include/node.h | 20 +++++++++----------- server/src/cfg.c | 14 +++++++------- server/src/node.c | 10 +++++----- server/src/receive.c | 2 +- server/src/send.c | 2 +- server/src/test.c | 2 +- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/server/include/node.h b/server/include/node.h index 82cc1306b..24913e60a 100644 --- a/server/include/node.h +++ b/server/include/node.h @@ -27,14 +27,14 @@ #include "list.h" /* Helper macros for virtual node type */ -#define node_type(n) ((n)->vt->type) -#define node_print(n) ((n)->vt->print(n)) +#define node_type(n) ((n)->_vt->type) +#define node_print(n) ((n)->_vt->print(n)) -#define node_read(n, p, ps, f, c) ((n)->vt->read(n, p, ps, f, c)) -#define node_write(n, p, ps, f, c) ((n)->vt->write(n, p, ps, f, c)) +#define node_read(n, p, ps, f, c) ((n)->_vt->read(n, p, ps, f, c)) +#define node_write(n, p, ps, f, c) ((n)->_vt->write(n, p, ps, f, c)) -#define node_read_single(n, m) ((n)->vt->read(n, m, 1, 0, 1)) -#define node_write_single(n, m) ((n)->vt->write(n, m, 1, 0, 1)) +#define node_read_single(n, m) ((n)->_vt->read(n, m, 1, 0, 1)) +#define node_write_single(n, m) ((n)->_vt->write(n, m, 1, 0, 1)) #define REGISTER_NODE_TYPE(type, name, fnc) \ @@ -49,9 +49,7 @@ __attribute__((constructor)) void __register_node_ ## fnc () { \ extern struct list node_types; -/** C++ like vtable construct for node_types - * @todo Add comments - */ +/** C++ like vtable construct for node_types */ struct node_type { /** The unique name of this node. This must be allways the first member! */ char *name; @@ -166,7 +164,7 @@ struct node int combine; /**< Number of messages to send / recv at once (scatter / gather) */ int affinity; /**< CPU Affinity of this node */ - struct node_type *vt; /**< C++ like virtual function call table */ + struct node_type *_vt; /**< C++ like virtual function call table */ union { struct socket *socket; @@ -174,7 +172,7 @@ struct node struct gtfpga *gtfpga; struct file *file; struct ngsi *ngsi; - }; /** Virtual data (used by vtable functions) */ + }; /** Virtual data (used by struct node::_vt functions) */ config_setting_t *cfg; /**< A pointer to the libconfig object which instantiated this node */ }; diff --git a/server/src/cfg.c b/server/src/cfg.c index e2b9dda4c..827fd257a 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -150,11 +150,11 @@ int config_parse_path(config_setting_t *cfg, if (enabled) { p->in->refcnt++; - p->in->vt->refcnt++; + p->in->_vt->refcnt++; list_foreach(struct node *node, &p->destinations) { node->refcnt++; - node->vt->refcnt++; + node->_vt->refcnt++; } list_push(paths, p); @@ -173,9 +173,9 @@ int config_parse_path(config_setting_t *cfg, /* Increment reference counters */ r->in->refcnt++; - r->in->vt->refcnt++; + r->in->_vt->refcnt++; r->out->refcnt++; - r->out->vt->refcnt++; + r->out->_vt->refcnt++; if (cfg_hook) config_parse_hooklist(cfg_hook, &r->hooks); @@ -265,11 +265,11 @@ int config_parse_node(config_setting_t *cfg, struct list *nodes, struct settings if (!config_setting_lookup_int(cfg, "affinity", &n->combine)) n->affinity = set->affinity; - n->vt = list_lookup(&node_types, type); - if (!n->vt) + n->_vt = list_lookup(&node_types, type); + if (!n->_vt) cerror(cfg, "Invalid type for node '%s'", n->name); - ret = n->vt->parse(cfg, n); + ret = n->_vt->parse(cfg, n); if (!ret) list_push(nodes, n); diff --git a/server/src/node.c b/server/src/node.c index 71b3dba29..4a87955eb 100644 --- a/server/src/node.c +++ b/server/src/node.c @@ -66,11 +66,11 @@ int node_start(struct node *n) } char *buf = node_print(n); - debug(1, "Starting node '%s' of type '%s' (%s)", n->name, n->vt->name, buf); + debug(1, "Starting node '%s' of type '%s' (%s)", n->name, n->_vt->name, buf); free(buf); { INDENT - return n->vt->open(n); + return n->_vt->open(n); } } @@ -80,7 +80,7 @@ int node_stop(struct node *n) info("Stopping node '%s'", n->name); { INDENT - ret = n->vt->close(n); + ret = n->_vt->close(n); } return ret; @@ -88,7 +88,7 @@ int node_stop(struct node *n) void node_reverse(struct node *n) { - switch (n->vt->type) { + switch (n->_vt->type) { #ifdef ENABLE_SOCKET case BSD_SOCKET: SWAP(n->socket->remote, n->socket->local); @@ -108,7 +108,7 @@ struct node * node_create() void node_destroy(struct node *n) { - switch (n->vt->type) { + switch (n->_vt->type) { #ifdef ENABLE_NGSI case NGSI: json_decref(n->ngsi->context); diff --git a/server/src/receive.c b/server/src/receive.c index 7e99756c2..7ba8a33fd 100644 --- a/server/src/receive.c +++ b/server/src/receive.c @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) node_reverse(node); node->refcnt++; - node->vt->refcnt++; + node->_vt->refcnt++; node_init(argc-optind, argv+optind, &set); node_start(node); diff --git a/server/src/send.c b/server/src/send.c index 2fc0d8c65..501087442 100644 --- a/server/src/send.c +++ b/server/src/send.c @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) node_reverse(node); node->refcnt++; - node->vt->refcnt++; + node->_vt->refcnt++; info("Initialize node types"); node_init(argc-optind, argv+optind, &set); diff --git a/server/src/test.c b/server/src/test.c index eeb8e8672..a64098100 100644 --- a/server/src/test.c +++ b/server/src/test.c @@ -97,7 +97,7 @@ int main(int argc, char *argv[]) error("There's no node with the name '%s'", argv[3]); node->refcnt++; - node->vt->refcnt++; + node->_vt->refcnt++; node_init(argc-3, argv+3, &settings); node_start(node); From 7d1bb5091507714f1c3003b03a215fe638153066 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 12 Oct 2015 19:03:36 +0200 Subject: [PATCH 57/81] abstract access to node vtable functions --- server/include/node.h | 3 +++ server/src/node.c | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/server/include/node.h b/server/include/node.h index 24913e60a..a4cceae15 100644 --- a/server/include/node.h +++ b/server/include/node.h @@ -28,11 +28,14 @@ /* Helper macros for virtual node type */ #define node_type(n) ((n)->_vt->type) +#define node_parse(n, cfg) ((n)->_vt->parse(n, cfg)) #define node_print(n) ((n)->_vt->print(n)) #define node_read(n, p, ps, f, c) ((n)->_vt->read(n, p, ps, f, c)) #define node_write(n, p, ps, f, c) ((n)->_vt->write(n, p, ps, f, c)) +#define node_open(n) ((n)->_vt->open(n)) +#define node_close(n) ((n)->_vt->close(n)) #define node_read_single(n, m) ((n)->_vt->read(n, m, 1, 0, 1)) #define node_write_single(n, m) ((n)->_vt->write(n, m, 1, 0, 1)) diff --git a/server/src/node.c b/server/src/node.c index 4a87955eb..52d54114e 100644 --- a/server/src/node.c +++ b/server/src/node.c @@ -70,7 +70,7 @@ int node_start(struct node *n) free(buf); { INDENT - return n->_vt->open(n); + return node_open(n); } } @@ -80,7 +80,7 @@ int node_stop(struct node *n) info("Stopping node '%s'", n->name); { INDENT - ret = n->_vt->close(n); + ret = node_close(n); } return ret; @@ -88,7 +88,7 @@ int node_stop(struct node *n) void node_reverse(struct node *n) { - switch (n->_vt->type) { + switch (node_type(n)) { #ifdef ENABLE_SOCKET case BSD_SOCKET: SWAP(n->socket->remote, n->socket->local); @@ -108,7 +108,7 @@ struct node * node_create() void node_destroy(struct node *n) { - switch (n->_vt->type) { + switch (node_type(n)) { #ifdef ENABLE_NGSI case NGSI: json_decref(n->ngsi->context); From 090f8fe9aff28c691a0ded7f5f7b28dbc569b628 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 10:07:31 +0200 Subject: [PATCH 58/81] reorganized contrib directory --- contrib/{ => fiware}/fiware-query.sh | 0 contrib/{ => fiware}/fiware-update.sh | 0 contrib/{ => tc}/tc-dump.sh | 0 contrib/{ => tc}/tc-netem.sh | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename contrib/{ => fiware}/fiware-query.sh (100%) rename contrib/{ => fiware}/fiware-update.sh (100%) rename contrib/{ => tc}/tc-dump.sh (100%) rename contrib/{ => tc}/tc-netem.sh (100%) diff --git a/contrib/fiware-query.sh b/contrib/fiware/fiware-query.sh similarity index 100% rename from contrib/fiware-query.sh rename to contrib/fiware/fiware-query.sh diff --git a/contrib/fiware-update.sh b/contrib/fiware/fiware-update.sh similarity index 100% rename from contrib/fiware-update.sh rename to contrib/fiware/fiware-update.sh diff --git a/contrib/tc-dump.sh b/contrib/tc/tc-dump.sh similarity index 100% rename from contrib/tc-dump.sh rename to contrib/tc/tc-dump.sh diff --git a/contrib/tc-netem.sh b/contrib/tc/tc-netem.sh similarity index 100% rename from contrib/tc-netem.sh rename to contrib/tc/tc-netem.sh From 8fe60f38ccdf89fa403530758281ad32e4673b9b Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 10:08:19 +0200 Subject: [PATCH 59/81] applying code style rules --- clients/opal/udp/models/send_receive/src/s2ss.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/clients/opal/udp/models/send_receive/src/s2ss.c b/clients/opal/udp/models/send_receive/src/s2ss.c index 4fa63cb9d..4b1858ee6 100644 --- a/clients/opal/udp/models/send_receive/src/s2ss.c +++ b/clients/opal/udp/models/send_receive/src/s2ss.c @@ -25,12 +25,13 @@ #include #if defined(__QNXNTO__) -# include -# include -# include -# include + #include + #include + #include + #include #elif defined(__linux__) -# define _GNU_SOURCE 1 + #define _GNU_SOURCE 1 + #include #endif /* Define RTLAB before including OpalPrint.h for messages to be sent From 790c85c066e7dab04f7e447742ae50e100e563a9 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 10:08:44 +0200 Subject: [PATCH 60/81] Added timestamp to OPAL-UDP client messages --- clients/opal/udp/models/send_receive/src/s2ss.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/clients/opal/udp/models/send_receive/src/s2ss.c b/clients/opal/udp/models/send_receive/src/s2ss.c index 4b1858ee6..9e88a2eac 100644 --- a/clients/opal/udp/models/send_receive/src/s2ss.c +++ b/clients/opal/udp/models/send_receive/src/s2ss.c @@ -128,13 +128,17 @@ static void *SendToIPPort(void *arg) /* Read data from the model */ OpalGetAsyncSendIconData(mdldata, mdldata_size, SendID); + /* Get current time */ + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + msg.length = mdldata_size / sizeof(double); for (i = 0; i < msg.length; i++) msg.data[i].f = (float) mdldata[i]; - /* Convert to network byte order */ msg.sequence = seq++; - msg.length = msg.length; + msg.ts.sec = now.tv_sec; + msg.ts.nsec = now.tc_nsec; /* Perform the actual write to the ip port */ if (SendPacket((char *) &msg, MSG_LEN(&msg)) < 0) From 9ab3b80bf99c7da6679392decfb98dec88698c36 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 10:09:35 +0200 Subject: [PATCH 61/81] apply code style rules --- server/include/node.h | 8 ++++---- server/src/hooks.c | 2 +- server/src/opal.c | 1 + server/src/path.c | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/server/include/node.h b/server/include/node.h index a4cceae15..185246c3f 100644 --- a/server/include/node.h +++ b/server/include/node.h @@ -28,7 +28,7 @@ /* Helper macros for virtual node type */ #define node_type(n) ((n)->_vt->type) -#define node_parse(n, cfg) ((n)->_vt->parse(n, cfg)) +#define node_parse(n, cfg) ((n)->_vt->parse(cfg, n)) #define node_print(n) ((n)->_vt->print(n)) #define node_read(n, p, ps, f, c) ((n)->_vt->read(n, p, ps, f, c)) @@ -36,6 +36,7 @@ #define node_open(n) ((n)->_vt->open(n)) #define node_close(n) ((n)->_vt->close(n)) + #define node_read_single(n, m) ((n)->_vt->read(n, m, 1, 0, 1)) #define node_write_single(n, m) ((n)->_vt->write(n, m, 1, 0, 1)) @@ -56,15 +57,14 @@ extern struct list node_types; struct node_type { /** The unique name of this node. This must be allways the first member! */ char *name; - - /** Node type */ + enum { BSD_SOCKET, /**< BSD Socket API */ LOG_FILE, /**< File IO */ OPAL_ASYNC, /**< OPAL-RT Asynchronous Process Api */ GTFPGA, /**< Xilinx ML507 GTFPGA card */ NGSI /**< NGSI 9/10 HTTP RESTful API (FIRWARE ContextBroker) */ - } type; + } type; /**< Node type */ /** Parse node connection details.โ€š * diff --git a/server/src/hooks.c b/server/src/hooks.c index 8d35e3ef9..65468d096 100644 --- a/server/src/hooks.c +++ b/server/src/hooks.c @@ -379,7 +379,7 @@ int hook_stats(struct path *p, struct hook *h, int when) if (p->received > 0) log_print(STATS, "%-40s|%10.2g|%10.2f|%10u|%10u|%10u|%10u|%10u|%10u|%10u|", buf, p->hist_owd.last, 1 / p->hist_gap_msg.last, - p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun, p->current->length + p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun, list_length(p->current) ); else log_print(STATS, "%-40s|%10s|%10s|%10u|%10u|%10u|%10u|%10u|%10u|%10s|", buf, "", "", diff --git a/server/src/opal.c b/server/src/opal.c index fe36076b7..76d90c463 100644 --- a/server/src/opal.c +++ b/server/src/opal.c @@ -14,6 +14,7 @@ #include "opal.h" #include "utils.h" +/** Global OPAL specific settings */ static struct opal_global *og = NULL; /** List of all opal nodes */ diff --git a/server/src/path.c b/server/src/path.c index e5de77570..ac0e10dce 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -135,7 +135,7 @@ static void * path_run(void *arg) int path_start(struct path *p) { INDENT char *buf = path_print(p); - info("Starting path: %s (poolsize = %u, msgsize = %u, #hooks = %zu, rate = %.1f)", + info("Starting path: %s (poolsize=%u, msgsize=%u, #hooks=%zu, rate=%.1f)", buf, p->poolsize, p->msgsize, list_length(&p->hooks), p->rate); free(buf); From cb75a2f4da2d36dc5e2379823b4d2b3029ccaf22 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 10:12:35 +0200 Subject: [PATCH 62/81] fixing memory leaks detected by valgrind --- server/src/if.c | 2 +- server/src/nl.c | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/server/src/if.c b/server/src/if.c index 2b4783b74..c1d917195 100644 --- a/server/src/if.c +++ b/server/src/if.c @@ -138,7 +138,7 @@ int if_get_egress(struct sockaddr *sa, struct rtnl_link **link) ? nl_addr_build(sin->sin_family, &sin->sin_addr.s_addr, sizeof(sin->sin_addr.s_addr)) : nl_addr_build(sin6->sin6_family, sin6->sin6_addr.s6_addr, sizeof(sin6->sin6_addr)); - ifindex = nl_get_egress(addr); + ifindex = nl_get_egress(addr); nl_addr_put(addr); if (ifindex < 0) error("Netlink error: %s", nl_geterror(ifindex)); break; diff --git a/server/src/nl.c b/server/src/nl.c index 7aced99bd..302d53a39 100644 --- a/server/src/nl.c +++ b/server/src/nl.c @@ -67,7 +67,9 @@ int nl_get_egress(struct nl_addr *addr) { int ret; struct nl_sock *sock = nl_init(); + struct nl_cb *cb; struct nl_msg *msg = nlmsg_alloc_simple(RTM_GETROUTE, 0); + struct rtnl_route *route = NULL; /* Build message */ struct rtmsg rmsg = { @@ -77,32 +79,37 @@ int nl_get_egress(struct nl_addr *addr) ret = nlmsg_append(msg, &rmsg, sizeof(rmsg), NLMSG_ALIGNTO); if (ret) - return ret; + goto out; + ret = nla_put_addr(msg, RTA_DST, addr); if (ret) - return ret; + goto out; /* Send message */ ret = nl_send_auto(sock, msg); - nlmsg_free(msg); if (ret < 0) - return ret; + goto out; /* Hook into receive chain */ - struct rtnl_route *route = NULL; - struct nl_cb *cb = nl_cb_alloc(NL_CB_VALID); + cb = nl_cb_alloc(NL_CB_VALID); nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, egress_cb, &route); /* Receive message */ nl_recvmsgs_report(sock, cb); nl_wait_for_ack(sock); - nl_cb_put(cb); /* Check result */ - if (route && (1 <= rtnl_route_get_nnexthops(route))) { - struct rtnl_nexthop *nh = rtnl_route_nexthop_n(route, 0); - return rtnl_route_nh_get_ifindex(nh); + if (!route || rtnl_route_get_nnexthops(route) != 1) { + ret = -1; + goto out2; } - else - return -1; + + ret = rtnl_route_nh_get_ifindex(rtnl_route_nexthop_n(route, 0)); + + rtnl_route_put(route); + +out2: nl_cb_put(cb); +out: nlmsg_free(msg); + + return ret; } From c5487663cc49c6599c36bacac3cb143575a32442 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 12:06:50 +0200 Subject: [PATCH 63/81] improved statistics and debug outputs --- server/include/log.h | 6 +++++- server/src/hist.c | 25 ++++++++++--------------- server/src/hooks.c | 6 +++--- server/src/log.c | 9 +++++++++ server/src/socket.c | 2 +- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/server/include/log.h b/server/include/log.h index 375c3d394..fcc8b4e18 100644 --- a/server/include/log.h +++ b/server/include/log.h @@ -78,7 +78,11 @@ void info(const char *fmt, ...) /** Printf alike warning message. */ void warn(const char *fmt, ...) __attribute__ ((format(printf, 1, 2))); - + +/** Printf alike statistics message. */ +void stats(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + /** Print error and exit. */ void error(const char *fmt, ...) __attribute__ ((format(printf, 1, 2))); diff --git a/server/src/hist.c b/server/src/hist.c index f0856d2f7..5a93b7ccb 100644 --- a/server/src/hist.c +++ b/server/src/hist.c @@ -103,23 +103,16 @@ double hist_stddev(struct hist *h) void hist_print(struct hist *h) { INDENT - info("Total: %u values", h->total); - info("Highest value: %f", h->highest); - info("Lowest value: %f", h->lowest); - info("Mean: %f", hist_mean(h)); - info("Variance: %f", hist_var(h)); - info("Standard derivation: %f", hist_stddev(h)); - if (h->higher > 0) - warn("Missed: %u values above %f", h->higher, h->high); - if (h->lower > 0) - warn("Missed: %u values below %f", h->lower, h->low); + stats("Counted values: %u (%u between %f and %f)", h->total, h->total-h->higher-h->lower, h->high, h->low); + stats("Highest: %f Lowest: %f", h->highest, h->lowest); + stats("Mu: %f Sigma2: %f Sigma: %f", hist_mean(h), hist_var(h), hist_stddev(h)); if (h->total - h->higher - h->lower > 0) { - hist_plot(h); - char *buf = hist_dump(h); - info("Matlab: %s", buf); + stats("Matlab: %s", buf); free(buf); + + hist_plot(h); } } @@ -137,13 +130,15 @@ void hist_plot(struct hist *h) } /* Print plot */ - info("%3s | %9s | %5s | %s", "#", "Value", "Occur", "Plot"); + stats("%9s | %5s | %s", "Value", "Count", "Plot"); line(); for (int i = 0; i < h->length; i++) { int bar = HIST_HEIGHT * ((double) h->data[i] / max); + if (bar == 0) + continue; - info("%3u | %+9g | " "%5u" " | %.*s", i, VAL(h, i), h->data[i], bar, buf); + stats("%+9g | " "%5u" " | %.*s", VAL(h, i), h->data[i], bar, buf); } } diff --git a/server/src/hooks.c b/server/src/hooks.c index 65468d096..456ff0cc5 100644 --- a/server/src/hooks.c +++ b/server/src/hooks.c @@ -377,12 +377,12 @@ int hook_stats(struct path *p, struct hook *h, int when) char *buf = path_print(p); if (p->received > 0) - log_print(STATS, "%-40s|%10.2g|%10.2f|%10u|%10u|%10u|%10u|%10u|%10u|%10u|", buf, + stats("%-40s|%10.2g|%10.2f|%10u|%10u|%10u|%10u|%10u|%10u|%10u|", buf, p->hist_owd.last, 1 / p->hist_gap_msg.last, p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun, list_length(p->current) ); else - log_print(STATS, "%-40s|%10s|%10s|%10u|%10u|%10u|%10u|%10u|%10u|%10s|", buf, "", "", + stats("%-40s|%10s|%10s|%10u|%10u|%10u|%10u|%10u|%10u|%10s|", buf, "", "", p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun, "" ); @@ -398,7 +398,7 @@ void hook_stats_header() { #define UNIT(u) "(" YEL(u) ")" - log_print(STATS, "%-40s|%19s|%19s|%19s|%19s|%19s|%19s|%19s|%10s|%10s|", "Source " MAG("=>") " Destination", + stats("%-40s|%19s|%19s|%19s|%19s|%19s|%19s|%19s|%10s|%10s|", "Source " MAG("=>") " Destination", "OWD" UNIT("S") " ", "Rate" UNIT("p/S") " ", "Sent" UNIT("p") " ", diff --git a/server/src/log.c b/server/src/log.c index f99a33e1e..87b263283 100644 --- a/server/src/log.c +++ b/server/src/log.c @@ -136,6 +136,15 @@ void warn(const char *fmt, ...) va_end(ap); } +void stats(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_vprint(STATS, fmt, ap); + va_end(ap); +} + void error(const char *fmt, ...) { va_list ap; diff --git a/server/src/socket.c b/server/src/socket.c index fded7353b..b45113429 100644 --- a/server/src/socket.c +++ b/server/src/socket.c @@ -266,7 +266,7 @@ int socket_write(struct node *n, struct msg *pool, int poolsize, int first, int if (bytes < 0) serror("Failed send"); - debug(10, "Sent packet of %u bytes: %u samples a %u values per sample", bytes, cnt, (bytes / cnt) / 4 - 4); + debug(17, "Sent packet of %u bytes: %u samples a %u values per sample", bytes, cnt, (bytes / cnt) / 4 - 4); return sent; } From ef2bcda344d3748de041abba054cc4a40cf3a5fa Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 15:01:15 +0200 Subject: [PATCH 64/81] smaller cleanups --- server/src/path.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/src/path.c b/server/src/path.c index ac0e10dce..8e31b78af 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -32,7 +32,7 @@ static void path_write(struct path *p) n->combine /* Number of messages which should be sent */ ); - debug(15, "Sent %u messages to node '%s'", sent, n->name); + debug(15, "Sent %u messages to node '%s'", sent, n->name); p->sent += sent; p->ts_sent = time_now(); /** @todo use hardware timestamps for socket node type */ @@ -140,10 +140,9 @@ int path_start(struct path *p) free(buf); /* We sort the hooks according to their priority before starting the path */ - int hook_cmp(const void *a, const void *b) { + list_sort(&p->hooks, ({int cmp(const void *a, const void *b) { return ((struct hook *) a)->priority - ((struct hook *) b)->priority; - } - list_sort(&p->hooks, hook_cmp); + }; &cmp; })); if (path_run_hook(p, HOOK_PATH_START)) return -1; From 41cd1402bea40ef64b338199225c47cff91f832c Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 15:05:48 +0200 Subject: [PATCH 65/81] improved node creation --- server/include/node.h | 7 ++++--- server/src/cfg.c | 36 +++++++++++++++++++++--------------- server/src/node.c | 8 ++++++-- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/server/include/node.h b/server/include/node.h index 185246c3f..c498b080f 100644 --- a/server/include/node.h +++ b/server/include/node.h @@ -56,7 +56,7 @@ extern struct list node_types; /** C++ like vtable construct for node_types */ struct node_type { /** The unique name of this node. This must be allways the first member! */ - char *name; + const char *name; enum { BSD_SOCKET, /**< BSD Socket API */ @@ -162,7 +162,8 @@ struct node_type { */ struct node { - char *name; /**< A short identifier of the node, only used for configuration and logging */ + const char *name; /**< A short identifier of the node, only used for configuration and logging */ + int refcnt; /**< How many paths are sending / receiving from this node? */ int combine; /**< Number of messages to send / recv at once (scatter / gather) */ int affinity; /**< CPU Affinity of this node */ @@ -231,7 +232,7 @@ int node_stop(struct node *n); void node_reverse(struct node *n); /** Create a node by allocating dynamic memory. */ -struct node * node_create(); +struct node * node_create(struct node_type *vt); /** Destroy node by freeing dynamically allocated memory. * diff --git a/server/src/cfg.c b/server/src/cfg.c index 827fd257a..acfb915b2 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -245,33 +245,39 @@ int config_parse_nodelist(config_setting_t *cfg, struct list *list, struct list int config_parse_node(config_setting_t *cfg, struct list *nodes, struct settings *set) { - const char *type; + const char *type, *name; int ret; - struct node *n = node_create(); + struct node *n; + struct node_type *vt; /* Required settings */ - n->cfg = cfg; - n->name = config_setting_name(cfg); - if (!n->name) - cerror(cfg, "Missing node name"); - if (!config_setting_lookup_string(cfg, "type", &type)) cerror(cfg, "Missing node type"); + + name = config_setting_name(cfg); + + vt = list_lookup(&node_types, type); + if (!vt) + cerror(cfg, "Invalid type for node '%s'", config_setting_name(cfg)); + + n = node_create(vt); + + n->name = name; + n->cfg = cfg; + + ret = node_parse(n, cfg); + if (ret) + cerror(cfg, "Failed to parse node '%s'", n->name); if (!config_setting_lookup_int(cfg, "combine", &n->combine)) n->combine = 1; if (!config_setting_lookup_int(cfg, "affinity", &n->combine)) n->affinity = set->affinity; - - n->_vt = list_lookup(&node_types, type); - if (!n->_vt) - cerror(cfg, "Invalid type for node '%s'", n->name); - - ret = n->_vt->parse(cfg, n); - if (!ret) - list_push(nodes, n); + + list_push(nodes, n); + list_push(&vt->nodes, n); return ret; } diff --git a/server/src/node.c b/server/src/node.c index 52d54114e..dfda0c8fe 100644 --- a/server/src/node.c +++ b/server/src/node.c @@ -101,9 +101,13 @@ void node_reverse(struct node *n) } } -struct node * node_create() +struct node * node_create(struct node_type *vt) { - return alloc(sizeof(struct node)); + struct node *n = alloc(sizeof(struct node)); + + n->_vt = vt; + + return n; } void node_destroy(struct node *n) From b26a92165def957c3ac0de7d97a10953c60bf426 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 15:40:34 +0200 Subject: [PATCH 66/81] allow paths without destination nodes (hooks are executed..) --- server/src/cfg.c | 17 ++++++----------- server/src/path.c | 14 +++++--------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/server/src/cfg.c b/server/src/cfg.c index acfb915b2..eaf7f7a34 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -101,34 +101,29 @@ int config_parse_global(config_setting_t *cfg, struct settings *set) int config_parse_path(config_setting_t *cfg, struct list *paths, struct list *nodes, struct settings *set) { + config_setting_t *cfg_in, *cfg_out, *cfg_hook; const char *in; - int enabled; - int reverse; + int enabled, reverse; struct path *p = path_create(); /* Input node */ - config_setting_t *cfg_in = config_setting_get_member(cfg, "in"); - if (!cfg_in || config_setting_type(cfg_in) != CONFIG_TYPE_STRING) - cerror(cfg, "Invalid input node for path"); + if (!config_setting_lookup_string(cfg, "in", &in)) + cerror(cfg, "Missing input node for path"); - in = config_setting_get_string(cfg_in); p->in = list_lookup(nodes, in); if (!p->in) cerror(cfg_in, "Invalid input node '%s'", in); /* Output node(s) */ - config_setting_t *cfg_out = config_setting_get_member(cfg, "out"); + cfg_out = config_setting_get_member(cfg, "out"); if (cfg_out) config_parse_nodelist(cfg_out, &p->destinations, nodes); - - if (list_length(&p->destinations) < 1) - cerror(cfg, "Missing output node for path"); p->out = (struct node *) list_first(&p->destinations); /* Optional settings */ - config_setting_t *cfg_hook = config_setting_get_member(cfg, "hook"); + cfg_hook = config_setting_get_member(cfg, "hook"); if (cfg_hook) config_parse_hooklist(cfg_hook, &p->hooks); diff --git a/server/src/path.c b/server/src/path.c index 8e31b78af..031b1383c 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -193,16 +193,12 @@ char * path_print(struct path *p) { char *buf = alloc(32); - strcatf(&buf, "%s " MAG("=>"), p->in->name); + strcatf(&buf, "%s " MAG("=>") " [", p->in->name); - if (list_length(&p->destinations) > 1) { - strcatf(&buf, " ["); - list_foreach(struct node *n, &p->destinations) - strcatf(&buf, " %s", n->name); - strcatf(&buf, " ]"); - } - else - strcatf(&buf, " %s", p->out->name); + list_foreach(struct node *n, &p->destinations) + strcatf(&buf, " %s", n->name); + + strcatf(&buf, " ]"); return buf; } From 3b437c1c0ca05a7d04995e5b2572335c1af9841d Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 16:11:58 +0200 Subject: [PATCH 67/81] do not interrupt curl_perform() during shutdown (leads to inconsistent state of Curl handle) --- server/src/ngsi.c | 9 +++++++-- server/src/server.c | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/server/src/ngsi.c b/server/src/ngsi.c index 9474d0736..24bf9d72d 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "ngsi.h" #include "utils.h" @@ -96,9 +97,13 @@ static int ngsi_request(CURL *handle, const char *endpoint, const char *operatio curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, strlen(post)); curl_easy_setopt(handle, CURLOPT_POSTFIELDS, post); - debug(20, "Request to context broker:\n%s", post); - + debug(18, "Request to context broker: %s\n%s", url, post); + + int old; /* We don't want to leave the CUrl handle in an invalid state */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); CURLcode ret = curl_easy_perform(handle); + pthread_setcancelstate(old, NULL); + if (ret) error("HTTP request failed: %s", curl_easy_strerror(ret)); diff --git a/server/src/server.c b/server/src/server.c index 2e2d6f0af..16002c25b 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -48,7 +48,7 @@ static void quit() list_destroy(&nodes); config_destroy(&config); - info("Goodbye!"); + info(GRN("Goodbye!")); _exit(EXIT_SUCCESS); } From 5bd0c9cf1ec68b8c616230b93c4d490ca433ae93 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 16:14:01 +0200 Subject: [PATCH 68/81] move checks to read/write functions --- server/src/gtfpga.c | 13 ++++++------- server/src/opal.c | 12 ++++++------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/server/src/gtfpga.c b/server/src/gtfpga.c index 72daded3a..8f753bbca 100644 --- a/server/src/gtfpga.c +++ b/server/src/gtfpga.c @@ -63,12 +63,6 @@ int gtfpga_parse(config_setting_t *cfg, struct node *n) const char *slot, *id, *err; config_setting_t *cfg_slot, *cfg_id; - /* Checks */ - if (n->combine != 1) { - config_setting_t *cfg_combine = config_setting_get_member(cfg, "combine"); - cerror(cfg_combine, "The GTFPGA node type does not support combining!"); - } - pci_filter_init(NULL, &g->filter); cfg_slot = config_setting_get_member(cfg, "slot"); @@ -247,6 +241,9 @@ int gtfpga_read(struct node *n, struct msg *pool, int poolsize, int first, int c struct gtfpga *g = n->gtfpga; struct msg *m = &pool[first % poolsize]; + + if (cnt != 1) + error("The GTFPGA node type does not support combining!"); uint64_t runs; read(g->fd_irq, &runs, sizeof(runs)); @@ -262,8 +259,10 @@ int gtfpga_read(struct node *n, struct msg *pool, int poolsize, int first, int c int gtfpga_write(struct node *n, struct msg *pool, int poolsize, int first, int cnt) { // struct gtfpga *g = n->gtfpga; - // struct msg *m = &pool[first % poolsize]; + + if (cnt != 1) + error("The GTFPGA node type does not support combining!"); return 0; } diff --git a/server/src/opal.c b/server/src/opal.c index 76d90c463..a7e90a094 100644 --- a/server/src/opal.c +++ b/server/src/opal.c @@ -135,12 +135,6 @@ int opal_parse(config_setting_t *cfg, struct node *n) { struct opal *o = alloc(sizeof(struct opal)); - /* Checks */ - if (n->combine != 1) { - config_setting_t *cfg_combine = config_setting_get_member(cfg, "combine"); - cerror(cfg_combine, "The OPAL-RT node type does not support combining!"); - } - config_setting_lookup_int(cfg, "send_id", &o->send_id); config_setting_lookup_int(cfg, "recv_id", &o->recv_id); config_setting_lookup_bool(cfg, "reply", &o->reply); @@ -206,6 +200,9 @@ int opal_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt struct msg *m = &pool[first % poolsize]; double data[MSG_VALUES]; + + if (cnt != 1) + error("The OPAL-RT node type does not support combining!"); /* This call unblocks when the 'Data Ready' line of a send icon is asserted. */ do { @@ -267,6 +264,9 @@ int opal_write(struct node *n, struct msg *pool, int poolsize, int first, int cn int state; int len; double data[m->length]; + + if (cnt != 1) + error("The OPAL-RT node type does not support combining!"); state = OpalGetAsyncModelState(); if ((state == STATE_RESET) || (state == STATE_STOP)) From 3c1c052041b298092e9959fcf0bba5586caba89b Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 16:16:33 +0200 Subject: [PATCH 69/81] fixed copy&paste mistake --- server/src/cfg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/cfg.c b/server/src/cfg.c index eaf7f7a34..838475b46 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -268,7 +268,7 @@ int config_parse_node(config_setting_t *cfg, struct list *nodes, struct settings if (!config_setting_lookup_int(cfg, "combine", &n->combine)) n->combine = 1; - if (!config_setting_lookup_int(cfg, "affinity", &n->combine)) + if (!config_setting_lookup_int(cfg, "affinity", &n->affinity)) n->affinity = set->affinity; list_push(nodes, n); From ef03551757ce7c273e9eb20eecb95347dea7e178 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 16:19:00 +0200 Subject: [PATCH 70/81] do not stop nodes, which haven't been started (this should be replaced by a more powerful state machine later) --- server/src/node.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/node.c b/server/src/node.c index dfda0c8fe..410a7c95b 100644 --- a/server/src/node.c +++ b/server/src/node.c @@ -77,6 +77,10 @@ int node_start(struct node *n) int node_stop(struct node *n) { INDENT int ret; + + if (!n->refcnt) /* Unused and not started. No reason to stop.. */ + return -1; + info("Stopping node '%s'", n->name); { INDENT From 75edd21a49d78a40be598ccc662a2c37f5370052 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 13 Oct 2015 16:22:07 +0200 Subject: [PATCH 71/81] fixed compiler warning --- server/src/cfg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/cfg.c b/server/src/cfg.c index 838475b46..23ac92837 100644 --- a/server/src/cfg.c +++ b/server/src/cfg.c @@ -101,7 +101,7 @@ int config_parse_global(config_setting_t *cfg, struct settings *set) int config_parse_path(config_setting_t *cfg, struct list *paths, struct list *nodes, struct settings *set) { - config_setting_t *cfg_in, *cfg_out, *cfg_hook; + config_setting_t *cfg_out, *cfg_hook; const char *in; int enabled, reverse; @@ -113,7 +113,7 @@ int config_parse_path(config_setting_t *cfg, p->in = list_lookup(nodes, in); if (!p->in) - cerror(cfg_in, "Invalid input node '%s'", in); + error("Invalid input node '%s'", in); /* Output node(s) */ cfg_out = config_setting_get_member(cfg, "out"); From 314e08550a44935079bff8424f2ec2fc50e2c302 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 14 Oct 2015 09:55:06 +0200 Subject: [PATCH 72/81] do not delay initial timer expiration --- server/src/gtfpga.c | 2 +- server/src/path.c | 2 +- server/src/random.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/gtfpga.c b/server/src/gtfpga.c index 8f753bbca..ecdcc1167 100644 --- a/server/src/gtfpga.c +++ b/server/src/gtfpga.c @@ -209,7 +209,7 @@ int gtfpga_open(struct node *n) struct itimerspec its = { .it_interval = time_from_double(1 / g->rate), - .it_value = { 1, 0 } + .it_value = { 0, 1 } }; ret = timerfd_settime(g->fd_irq, 0, &its, NULL); if (ret) diff --git a/server/src/path.c b/server/src/path.c index 031b1383c..a9297c5c2 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -151,7 +151,7 @@ int path_start(struct path *p) if (p->rate) { struct itimerspec its = { .it_interval = time_from_double(1 / p->rate), - .it_value = { 1, 0 } + .it_value = { 0, 1 } }; p->tfd = timerfd_create(CLOCK_REALTIME, 0); diff --git a/server/src/random.c b/server/src/random.c index 4e9da6238..321b95ec4 100644 --- a/server/src/random.c +++ b/server/src/random.c @@ -42,7 +42,7 @@ int main(int argc, char *argv[]) /* Setup timer */ struct itimerspec its = { .it_interval = time_from_double(1 / rate), - .it_value = { 1, 0 } + .it_value = { 0, 1 } }; int tfd = timerfd_create(CLOCK_REALTIME, 0); From d85db3f60420cdc44884cfeff0a32069f4e21831 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 14 Oct 2015 12:11:33 +0200 Subject: [PATCH 73/81] added file split functionality by rewriting most of the file stuff.. --- documentation/clients/File.md | 63 +++--- server/etc/example.conf | 28 +-- server/include/file.h | 37 ++-- server/src/file.c | 353 +++++++++++++++++++++------------- server/src/node.c | 6 +- 5 files changed, 299 insertions(+), 188 deletions(-) diff --git a/documentation/clients/File.md b/documentation/clients/File.md index 75428cd18..3015ea6f9 100644 --- a/documentation/clients/File.md +++ b/documentation/clients/File.md @@ -4,16 +4,14 @@ The `file` node-type can be used to log or replay samples to / from disk. ## Configuration -Every `file` node supports the following special settings: +Every `file` node can be configured to only read or write or to do both at the same time. +The node configuration is splitted in to groups: `in` and `out`. -#### `in` *(string: filesystem path)* +#### `path` *(string: filesystem path)* + +Specifies the path to a file from which is written to or read from (depending in which group is used). -Specifies the path to a file which contains data for replaying. See below for a description of the file format. - -#### `out` *(string: filesystem path)* - -Specifies the path to a file where samples will be written to. This setting allows to add special paceholders for time and date values. See [strftime(3)](http://man7.org/linux/man-pages/man3/strftime.3.html) for a list of supported placeholder. @@ -25,7 +23,7 @@ will create a file called: *path_of_working_directory*/logs/measurements_2015-08 See below for a description of the file format. -#### `file_mode` *(string)* +#### `mode` *(string)* Specifies the mode which should be used to open the output file. See [open(2)](http://man7.org/linux/man-pages/man2/open.2.html) for an explanation of allowed values. @@ -57,38 +55,53 @@ The supported values for `epoch_mode`: | `relative` | `epoch` | `first + epoch` | | `absolute` | `epoch - first` | `epoch` | -#### `send_rate` *(float)* +#### `rate` *(float)* By default `send_rate` has the value `0` which means that the time between consecutive samples is the same as in the `in` file based on the timestamps in the first column. If this setting has a non-zero value, the default behaviour is overwritten with a fixed rate. +#### `split` *(integer)* + +Only valid for the `out` group. + +Splits the output file every `split` mega-byte. This setting will append the chunk number to the `path` setting. + +Example: `data/my_measurements.log_001` + +#### `splitted` *(boolean)* + +Only valid for the `in` group. + +Expects the input data in splitted format. + ### Example file_node = { type = "file", ### The following settings are specific to the file node-type!! ### - mode = "w+", # The mode in which files should be opened (see open(2)) - # You might want to use "a+" to append to a file - - in = "logs/file_input.log", # These options specify the path prefix where the the files are stored - out = "logs/file_output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3)) - epoch_mode = "direct" # One of: - # direct (default) - # wait - # relative - # absolute + in = { + path = "logs/input.log", # These options specify the path prefix where the the files are stored + mode = "w+", # The mode in which files should be opened (see open(2)) + + epoch_mode = "direct" # One of: direct (default), wait, relative, absolute + epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0). + # Consult the documentation of a full explanation - epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0): - # - epoch_mode = now: The first value is read at: _now_ + epoch seconds. - # - epoch_mode = relative: The first value is read at _start_ + `epoch` seconds. - # - epoch_mode = absolute: The first value is read at epoch seconds after 1970-01-01 00:00:00. - - rate = 2.0 # A constant rate at which the lines of the input files should be read + rate = 2.0 # A constant rate at which the lines of the input files should be read # A missing or zero value will use the timestamp in the first column # of the file to determine the pause between consecutive lines. + + splitted = false + }, + out = { + path = "logs/output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3)) + mode = "a+" # You might want to use "a+" to append to a file + + split = 100, # Split output file every 100 MB + } } ## File Format diff --git a/server/etc/example.conf b/server/etc/example.conf index 8cd0e679e..916fc226a 100644 --- a/server/etc/example.conf +++ b/server/etc/example.conf @@ -87,20 +87,26 @@ nodes = { ### The following settings are specific to the file node-type!! ### - mode = "w+", # The mode in which files should be opened (see open(2)) - # You might want to use "a+" to append to a file - - in = "logs/file_input.log", # These options specify the path prefix where the the files are stored - out = "logs/file_output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3)) - - epoch_mode = "direct" # One of: direct (default), wait, relative, absolute - epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0). + in = { + path = "logs/input.log", # These options specify the path prefix where the the files are stored + mode = "w+", # The mode in which files should be opened (see open(2)) + + epoch_mode = "direct" # One of: direct (default), wait, relative, absolute + epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0). # Consult the documentation of a full explanation - - - rate = 2.0 # A constant rate at which the lines of the input files should be read + + rate = 2.0 # A constant rate at which the lines of the input files should be read # A missing or zero value will use the timestamp in the first column # of the file to determine the pause between consecutive lines. + + splitted = false + }, + out = { + path = "logs/output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3)) + mode = "a+" # You might want to use "a+" to append to a file + + split = 100, # Split output file every 100 MB + } }, gtfpga_node = { type = "gtfpga", diff --git a/server/include/file.h b/server/include/file.h index ca633b749..c414f9904 100644 --- a/server/include/file.h +++ b/server/include/file.h @@ -20,31 +20,40 @@ #include "node.h" -#define FILE_MAX_PATHLEN 128 +#define FILE_MAX_PATHLEN 512 + +enum { + FILE_READ, + FILE_WRITE +}; struct file { - FILE *in; - FILE *out; + struct file_direction { + FILE *handle; /**< libc: stdio file handle */ - char *path_in; - char *path_out; + const char *mode; /**< libc: fopen() mode */ + const char *fmt; /**< Format string for file name. */ - const char *file_mode; /**< The mode for fopen() which is used for the out file. */ + char *path; /**< Real file name */ + + int chunk; /**< Current chunk number. */ + int split; /**< Split file every file::split mega bytes. */ + } read, write; - enum epoch_mode { + enum read_epoch_mode { EPOCH_DIRECT, EPOCH_WAIT, EPOCH_RELATIVE, EPOCH_ABSOLUTE - } epoch_mode; /**< Specifies how file::offset is calculated. */ + } read_epoch_mode; /**< Specifies how file::offset is calculated. */ - struct timespec first; /**< The first timestamp in the file file::path_in */ - struct timespec epoch; /**< The epoch timestamp from the configuration. */ - struct timespec offset; /**< An offset between the timestamp in the input file and the current time */ + struct timespec read_first; /**< The first timestamp in the file file::path_in */ + struct timespec read_epoch; /**< The epoch timestamp from the configuration. */ + struct timespec read_offset; /**< An offset between the timestamp in the input file and the current time */ - double rate; /**< The sending rate. */ - int tfd; /**< Timer file descriptor. Blocks until 1 / rate seconds are elapsed. */ - int sequence; /**< Last sequence of this node */ + int read_sequence; /**< Sequence number of last message which has been written to file::path_out */ + int read_timer; /**< Timer file descriptor. Blocks until 1 / rate seconds are elapsed. */ + double read_rate; /**< The read rate. */ }; /** @see node_vtable::init */ diff --git a/server/src/file.c b/server/src/file.c index f3b626320..8fe33ad5a 100644 --- a/server/src/file.c +++ b/server/src/file.c @@ -26,49 +26,146 @@ int file_deinit() return 0; /* nothing todo here */ } +static char * file_format_name(const char *format, struct timespec *ts) +{ + struct tm tm; + char *buf = alloc(FILE_MAX_PATHLEN); + + /* Convert time */ + gmtime_r(&ts->tv_sec, &tm); + + strftime(buf, FILE_MAX_PATHLEN, format, &tm); + + return buf; +} + +static FILE * file_reopen(struct file_direction *dir) +{ + char buf[FILE_MAX_PATHLEN]; + const char *path = buf; + + /* Append chunk number to filename */ + if (dir->chunk >= 0) + snprintf(buf, FILE_MAX_PATHLEN, "%s_%03u", dir->path, dir->chunk); + else + path = dir->path; + + if (dir->handle) + fclose(dir->handle); + + return fopen(path, dir->mode); +} + +static int file_parse_direction(config_setting_t *cfg, struct file *f, int d) +{ + struct file_direction *dir = (d == FILE_READ) ? &f->read : &f->write; + + if (!config_setting_lookup_string(cfg, "path", &dir->fmt)) + return -1; + + if (!config_setting_lookup_string(cfg, "mode", &dir->mode)) + dir->mode = (d == FILE_READ) ? "r" : "w+"; + + return 0; +} + +int file_parse(config_setting_t *cfg, struct node *n) +{ + struct file *f = alloc(sizeof(struct file)); + + config_setting_t *cfg_in, *cfg_out; + + cfg_out = config_setting_get_member(cfg, "out"); + if (cfg_out) { + if (file_parse_direction(cfg_out, f, FILE_WRITE)) + cerror(cfg_out, "Failed to parse output file for node '%s'", n->name); + + /* More write specific settings */ + if (!config_setting_lookup_int(cfg_out, "split", &f->write.split)) + f->write.split = 0; /* Save all samples in a single file */ + } + + cfg_in = config_setting_get_member(cfg, "in"); + if (cfg_in) { + if (file_parse_direction(cfg_in, f, FILE_READ)) + cerror(cfg_in, "Failed to parse input file for node '%s'", n->name); + + /* More read specific settings */ + if (!config_setting_lookup_bool(cfg_in, "splitted", &f->read.split)) + f->read.split = 0; /* Save all samples in a single file */ + if (!config_setting_lookup_float(cfg_in, "rate", &f->read_rate)) + f->read_rate = 0; /* Disable fixed rate sending. Using timestamps of file instead */ + + double epoch_flt; + if (!config_setting_lookup_float(cfg_in, "epoch", &epoch_flt)) + epoch_flt = 0; + + f->read_epoch = time_from_double(epoch_flt); + + const char *epoch_mode; + if (!config_setting_lookup_string(cfg_in, "epoch_mode", &epoch_mode)) + epoch_mode = "direct"; + + if (!strcmp(epoch_mode, "direct")) + f->read_epoch_mode = EPOCH_DIRECT; + else if (!strcmp(epoch_mode, "wait")) + f->read_epoch_mode = EPOCH_WAIT; + else if (!strcmp(epoch_mode, "relative")) + f->read_epoch_mode = EPOCH_RELATIVE; + else if (!strcmp(epoch_mode, "absolute")) + f->read_epoch_mode = EPOCH_ABSOLUTE; + else + cerror(cfg_in, "Invalid value '%s' for setting 'epoch_mode'", epoch_mode); + } + + n->file = f; + + return 0; +} + char * file_print(struct node *n) { struct file *f = n->file; char *buf = NULL; - if (f->path_in) { - const char *epoch_mode_str = NULL; - switch (f->epoch_mode) { - case EPOCH_DIRECT: epoch_mode_str = "direct"; break; - case EPOCH_WAIT: epoch_mode_str = "wait"; break; - case EPOCH_RELATIVE: epoch_mode_str = "relative"; break; - case EPOCH_ABSOLUTE: epoch_mode_str = "absolute"; break; + if (f->read.fmt) { + const char *epoch_str = NULL; + switch (f->read_epoch_mode) { + case EPOCH_DIRECT: epoch_str = "direct"; break; + case EPOCH_WAIT: epoch_str = "wait"; break; + case EPOCH_RELATIVE: epoch_str = "relative"; break; + case EPOCH_ABSOLUTE: epoch_str = "absolute"; break; } strcatf(&buf, "in=%s, epoch_mode=%s, epoch=%.2f, ", - f->path_in, - epoch_mode_str, - time_to_double(&f->epoch) + f->read.path ? f->read.path : f->read.fmt, + epoch_str, + time_to_double(&f->read_epoch) ); - if (f->rate) - strcatf(&buf, "rate=%.1f, ", f->rate); + if (f->read_rate) + strcatf(&buf, "rate=%.1f, ", f->read_rate); } - if (f->path_out) { + if (f->write.fmt) { strcatf(&buf, "out=%s, mode=%s, ", - f->path_out, - f->file_mode + f->write.path ? f->write.path : f->write.fmt, + f->write.mode ); } - if (f->first.tv_sec || f->first.tv_nsec) - strcatf(&buf, "first=%.2f, ", time_to_double(&f->first)); + if (f->read_first.tv_sec || f->read_first.tv_nsec) + strcatf(&buf, "first=%.2f, ", time_to_double(&f->read_first)); - if (f->offset.tv_sec || f->offset.tv_nsec) - strcatf(&buf, "offset=%.2f, ", time_to_double(&f->offset)); + if (f->read_offset.tv_sec || f->read_offset.tv_nsec) + strcatf(&buf, "offset=%.2f, ", time_to_double(&f->read_offset)); - if ((f->first.tv_sec || f->first.tv_nsec) && - (f->offset.tv_sec || f->offset.tv_nsec)) { - struct timespec eta, now = time_now(); + if ((f->read_first.tv_sec || f->read_first.tv_nsec) && + (f->read_offset.tv_sec || f->read_offset.tv_nsec)) { + struct timespec eta, now = time_now(); - eta = time_add(&f->first, &f->offset); - eta = time_diff(&now, &eta); + eta = time_add(&f->read_first, &f->read_offset); + eta = time_diff(&now, &eta); if (eta.tv_sec || eta.tv_nsec) strcatf(&buf, "eta=%.2f sec, ", time_to_double(&eta)); @@ -80,123 +177,81 @@ char * file_print(struct node *n) return buf; } -int file_parse(config_setting_t *cfg, struct node *n) -{ - struct file *f = alloc(sizeof(struct file)); - - const char *out, *in; - const char *epoch_mode; - double epoch_flt; - - if (config_setting_lookup_string(cfg, "out", &out)) { - time_t t = time(NULL); - struct tm *tm = localtime(&t); - - f->path_out = alloc(FILE_MAX_PATHLEN); - if (strftime(f->path_out, FILE_MAX_PATHLEN, out, tm) == 0) - cerror(cfg, "Invalid path for output"); - - } - if (config_setting_lookup_string(cfg, "in", &in)) - f->path_in = strdup(in); - - if (!config_setting_lookup_string(cfg, "file_mode", &f->file_mode)) - f->file_mode = "w+"; - - if (!config_setting_lookup_float(cfg, "send_rate", &f->rate)) - f->rate = 0; /* Disable fixed rate sending. Using timestamps of file instead */ - - if (config_setting_lookup_float(n->cfg, "epoch", &epoch_flt)) - f->epoch = time_from_double(epoch_flt); - - if (!config_setting_lookup_string(n->cfg, "epoch_mode", &epoch_mode)) - epoch_mode = "direct"; - - if (!strcmp(epoch_mode, "direct")) - f->epoch_mode = EPOCH_DIRECT; - else if (!strcmp(epoch_mode, "wait")) - f->epoch_mode = EPOCH_WAIT; - else if (!strcmp(epoch_mode, "relative")) - f->epoch_mode = EPOCH_RELATIVE; - else if (!strcmp(epoch_mode, "absolute")) - f->epoch_mode = EPOCH_ABSOLUTE; - else - cerror(n->cfg, "Invalid value '%s' for setting 'epoch_mode'", epoch_mode); - - n->file = f; - - return 0; -} - int file_open(struct node *n) { struct file *f = n->file; + + struct timespec now = time_now(); - if (f->path_in) { + if (f->read.fmt) { + /* Prepare file name */ + f->read.chunk = f->read.split ? 0 : -1; + f->read.path = file_format_name(f->read.fmt, &now); + /* Open file */ - f->in = fopen(f->path_in, "r"); - if (!f->in) - serror("Failed to open file for reading: '%s'", f->path_in); + f->read.handle = file_reopen(&f->read); + if (!f->read.handle) + serror("Failed to open file for reading: '%s'", f->read.path); /* Create timer */ - f->tfd = timerfd_create(CLOCK_REALTIME, 0); - if (f->tfd < 0) + f->read_timer = timerfd_create(CLOCK_REALTIME, 0); + if (f->read_timer < 0) serror("Failed to create timer"); + /* Arm the timer with a fixed rate */ + if (f->read_rate) { + struct itimerspec its = { + .it_interval = time_from_double(1 / f->read_rate), + .it_value = { 0, 1 }, + }; + + int ret = timerfd_settime(f->read_timer, 0, &its, NULL); + if (ret) + serror("Failed to start timer"); + } + /* Get current time */ struct timespec now = time_now(); /* Get timestamp of first line */ struct msg m; - int ret = msg_fscan(f->in, &m, NULL, NULL); rewind(f->in); + int ret = msg_fscan(f->read.handle, &m, NULL, NULL); rewind(f->read.handle); if (ret < 0) error("Failed to read first timestamp of node '%s'", n->name); - f->first = MSG_TS(&m); + f->read_first = MSG_TS(&m); - /* Set offset depending on epoch_mode */ - switch (f->epoch_mode) { + /* Set read_offset depending on epoch_mode */ + switch (f->read_epoch_mode) { case EPOCH_DIRECT: /* read first value at now + epoch */ - f->offset = time_diff(&f->first, &now); - f->offset = time_add(&f->offset, &f->epoch); + f->read_offset = time_diff(&f->read_first, &now); + f->read_offset = time_add(&f->read_offset, &f->read_epoch); break; case EPOCH_WAIT: /* read first value at now + first + epoch */ - f->offset = now; - f->offset = time_add(&f->offset, &f->epoch); + f->read_offset = now; + f->read_offset = time_add(&f->read_offset, &f->read_epoch); break; case EPOCH_RELATIVE: /* read first value at first + epoch */ - f->offset = f->epoch; + f->read_offset = f->read_epoch; break; - case EPOCH_ABSOLUTE: /* read first value at f->epoch */ - f->offset = time_diff(&f->first, &f->epoch); + case EPOCH_ABSOLUTE: /* read first value at f->read_epoch */ + f->read_offset = time_diff(&f->read_first, &f->read_epoch); break; } - - /* Arm the timer with a fixed rate */ - if (f->rate) { - struct itimerspec its = { - .it_interval = time_from_double(1 / f->rate), - .it_value = { 1, 0 }, - }; - - int ret = timerfd_settime(f->tfd, 0, &its, NULL); - if (ret) - serror("Failed to start timer"); - } - - char *buf = file_print(n); - debug(4, "Opened file node '%s': %s", n->name, buf); - free(buf); } - if (f->path_out) { - /* Open output file */ - f->out = fopen(f->path_out, f->file_mode); - if (!f->out) - serror("Failed to open file for writing: '%s'", f->path_out); + if (f->write.fmt) { + /* Prepare file name */ + f->write.chunk = f->write.split ? 0 : -1; + f->write.path = file_format_name(f->write.fmt, &now); + + /* Open file */ + f->write.handle = file_reopen(&f->write); + if (!f->write.handle) + serror("Failed to open file for writing: '%s'", f->write.path); } return 0; @@ -205,13 +260,16 @@ int file_open(struct node *n) int file_close(struct node *n) { struct file *f = n->file; + + free(f->read.path); + free(f->write.path); - if (f->tfd) - close(f->tfd); - if (f->in) - fclose(f->in); - if (f->out) - fclose(f->out); + if (f->read_timer) + close(f->read_timer); + if (f->read.handle) + fclose(f->read.handle); + if (f->write.handle) + fclose(f->write.handle); return 0; } @@ -221,31 +279,54 @@ int file_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt int values, flags, i = 0; struct file *f = n->file; - if (f->in) { + if (f->read.handle) { for (i = 0; i < cnt; i++) { struct msg *cur = &pool[(first+i) % poolsize]; /* Get message and timestamp */ - values = msg_fscan(f->in, cur, &flags, NULL); +retry: values = msg_fscan(f->read.handle, cur, &flags, NULL); if (values < 0) { - if (!feof(f->in)) - warn("Failed to parse file of node '%s': reason=%d", n->name, values); + if (feof(f->read.handle)) { + if (f->read.split) { + f->read.chunk++; + f->read.handle = file_reopen(&f->read); + if (!f->read.handle) + return 0; + + info("Open new input chunk of node '%s': chunk=%u", n->name, f->read.chunk); + } + else { + info("Rewind input file of node '%s'", n->name); + rewind(f->read.handle); + goto retry; + } + } + else + warn("Failed to read messages from node '%s': reason=%d", n->name, values); return 0; } /* Fix missing sequence no */ - cur->sequence = f->sequence = (flags & MSG_PRINT_SEQUENCE) ? cur->sequence : f->sequence + 1; - - if (!f->rate || ftell(f->in) == 0) { - struct timespec until = time_add(&MSG_TS(cur), &f->offset); - - if (timerfd_wait_until(f->tfd, &until) < 0) + cur->sequence = f->read_sequence = (flags & MSG_PRINT_SEQUENCE) ? cur->sequence : f->read_sequence + 1; + + if (!f->read_rate || ftell(f->read.handle) == 0) { + struct timespec until = time_add(&MSG_TS(cur), &f->read_offset); + if (timerfd_wait_until(f->read_timer, &until) < 0) serror("Failed to wait for timer"); + + /* Update timestamp */ + cur->ts.sec = until.tv_sec; + cur->ts.nsec = until.tv_nsec; } else { /* Wait with fixed rate delay */ - if (timerfd_wait(f->tfd) < 0) + if (timerfd_wait(f->read_timer) < 0) serror("Failed to wait for timer"); + + /* Update timestamp */ + struct timespec now = time_now(); + cur->ts.sec = now.tv_sec; + cur->ts.nsec = now.tv_nsec; } } } @@ -260,17 +341,23 @@ int file_write(struct node *n, struct msg *pool, int poolsize, int first, int cn int i = 0; struct file *f = n->file; - if (f->out) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - + if (f->write.handle) { for (i = 0; i < cnt; i++) { + /* Split file if requested */ + if ((f->write.split > 0) && ftell(f->write.handle) > f->write.split * (1 << 20)) { + f->write.chunk++; + f->write.handle = file_reopen(&f->write); + + info("Splitted output file for node '%s': chunk=%u", n->name, f->write.chunk); + } + struct msg *m = &pool[(first+i) % poolsize]; - msg_fprint(f->out, m, MSG_PRINT_ALL & ~MSG_PRINT_OFFSET, 0); + msg_fprint(f->write.handle, m, MSG_PRINT_ALL & ~MSG_PRINT_OFFSET, 0); } + fflush(f->write.handle); } else - error("Can not write to node '%s", n->name); + error("Can not write to node '%s'", n->name); return i; } diff --git a/server/src/node.c b/server/src/node.c index 410a7c95b..a7b1a6f20 100644 --- a/server/src/node.c +++ b/server/src/node.c @@ -99,7 +99,7 @@ void node_reverse(struct node *n) break; #endif case LOG_FILE: - SWAP(n->file->path_in, n->file->path_out); + SWAP(n->file->read, n->file->write); break; default: { } } @@ -129,10 +129,6 @@ void node_destroy(struct node *n) rtnl_cls_put(n->socket->tc_classifier); break; #endif - case LOG_FILE: - free(n->file->path_in); - free(n->file->path_out); - break; default: { } } From ea74bcd5c86d478b46e507029695a2c41a8cb437 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 14 Oct 2015 12:12:53 +0200 Subject: [PATCH 74/81] Fix division by zero --- server/src/hooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/hooks.c b/server/src/hooks.c index 456ff0cc5..35404a8e4 100644 --- a/server/src/hooks.c +++ b/server/src/hooks.c @@ -376,7 +376,7 @@ int hook_stats(struct path *p, struct hook *h, int when) case HOOK_PERIODIC: { char *buf = path_print(p); - if (p->received > 0) + if (p->received > 1) stats("%-40s|%10.2g|%10.2f|%10u|%10u|%10u|%10u|%10u|%10u|%10u|", buf, p->hist_owd.last, 1 / p->hist_gap_msg.last, p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun, list_length(p->current) From f557f933f2753343cd785c778f8a3027b6db5182 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 14 Oct 2015 12:13:03 +0200 Subject: [PATCH 75/81] coding style --- server/src/socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/socket.c b/server/src/socket.c index b45113429..45f5696b4 100644 --- a/server/src/socket.c +++ b/server/src/socket.c @@ -41,7 +41,7 @@ static struct list sockets; int socket_init(int argc, char * argv[], struct settings *set) { INDENT if (check_root()) - error("The socket node-type requires superuser privileges!"); + error("The 'socket' node-type requires superuser privileges!"); nl_init(); /* Fill link cache */ list_init(&interfaces, (dtor_cb_t) if_destroy); From 1bf6d9e50ab45bea232b4bfc18fe09df9381f818 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 14 Oct 2015 12:13:28 +0200 Subject: [PATCH 76/81] do not start with sequence no 0 because we cant detect restarts this way --- server/src/random.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/src/random.c b/server/src/random.c index 321b95ec4..e8a46e07a 100644 --- a/server/src/random.c +++ b/server/src/random.c @@ -57,8 +57,6 @@ int main(int argc, char *argv[]) /* Block until 1/p->rate seconds elapsed */ while (limit-- > 0 || argc < 4) { - m.sequence += timerfd_wait(tfd); - struct timespec ts = time_now(); m.ts.sec = ts.tv_sec; @@ -66,8 +64,9 @@ int main(int argc, char *argv[]) msg_random(&m); msg_fprint(stdout, &m, MSG_PRINT_ALL & ~MSG_PRINT_OFFSET, 0); - fflush(stdout); + + m.sequence += timerfd_wait(tfd); } close(tfd); From ab8723ba8fb4972872069320aea5c80761c9703e Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 14 Oct 2015 12:13:47 +0200 Subject: [PATCH 77/81] skip hooks as soon as possible --- server/src/path.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/src/path.c b/server/src/path.c index a9297c5c2..014c4a24d 100644 --- a/server/src/path.c +++ b/server/src/path.c @@ -42,9 +42,14 @@ static void path_write(struct path *p) int path_run_hook(struct path *p, enum hook_type t) { int ret = 0; + list_foreach(struct hook *h, &p->hooks) { - if (h->type & t) - ret += ((hook_cb_t) h->cb)(p, h, t); + if (h->type & t) { + ret = ((hook_cb_t) h->cb)(p, h, t); + debug(22, "Running hook when=%u '%s' prio=%u ret=%d", t, h->name, h->priority, ret); + if (ret) + return ret; + } } return ret; From 14c03ee70c5ab36bc053fa9ae2b3ff711bc402d6 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 14 Oct 2015 12:18:25 +0200 Subject: [PATCH 78/81] fix histograms plots --- server/src/hist.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/src/hist.c b/server/src/hist.c index 5a93b7ccb..8d8b8b414 100644 --- a/server/src/hist.c +++ b/server/src/hist.c @@ -134,11 +134,12 @@ void hist_plot(struct hist *h) line(); for (int i = 0; i < h->length; i++) { - int bar = HIST_HEIGHT * ((double) h->data[i] / max); - if (bar == 0) - continue; + double value = VAL(h, i); + int cnt = h->data[i]; + int bar = HIST_HEIGHT * ((double) cnt / max); - stats("%+9g | " "%5u" " | %.*s", VAL(h, i), h->data[i], bar, buf); + if (value > h->lowest || value < h->highest) + stats("%+9g | " "%5u" " | %.*s", value, cnt, bar, buf); } } From 61e2bd723d6ae80f5bd19dc4be656c25c19e2148 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 14 Oct 2015 12:19:01 +0200 Subject: [PATCH 79/81] new NGSI node which polling support --- server/include/ngsi.h | 10 +- server/src/ngsi.c | 372 ++++++++++++++++++++++++++++-------------- server/src/node.c | 3 +- 3 files changed, 260 insertions(+), 125 deletions(-) diff --git a/server/include/ngsi.h b/server/include/ngsi.h index 4350f698d..44304b65a 100644 --- a/server/include/ngsi.h +++ b/server/include/ngsi.h @@ -32,6 +32,13 @@ struct node; +struct ngsi_mapping { + char *name; + char *type; + + int index; +}; + struct ngsi { const char *endpoint; /**< The NGSI context broker endpoint URL. */ const char *entity_id; /**< The context broker entity id related to this node */ @@ -39,14 +46,15 @@ struct ngsi { const char *access_token; /**< An optional authentication token which will be sent as HTTP header. */ double timeout; /**< HTTP timeout in seconds */ + double rate; /**< Rate used for polling. */ + int tfd; /**< Timer */ int ssl_verify; /**< Boolean flag whether SSL server certificates should be verified or not. */ struct curl_slist *headers; /**< List of HTTP request headers for libcurl */ CURL *curl; /**< libcurl: handle */ - json_t *context; /**< The complete JSON tree which will be used for contextUpdate requests */ struct list mapping; /**< A mapping between indices of the S2SS messages and the attributes in ngsi::context */ }; diff --git a/server/src/ngsi.c b/server/src/ngsi.c index 24bf9d72d..0680af4a7 100644 --- a/server/src/ngsi.c +++ b/server/src/ngsi.c @@ -19,13 +19,16 @@ #include #include #include +#include #include #include "ngsi.h" #include "utils.h" +#include "timing.h" extern struct settings settings; +#if 0 /* unused at the moment */ static json_t * json_uuid() { char eid[37]; @@ -37,6 +40,7 @@ static json_t * json_uuid() return json_string(eid); } + static json_t * json_date(struct timespec *ts) { // Example: 2015-09-21T11:42:25+02:00 @@ -61,6 +65,144 @@ static json_t * json_lookup(json_t *array, char *key, char *needle) return NULL; } +#endif + +static json_t* json_entity(struct ngsi *i, struct msg *pool, int poolsize, int first, int cnt) +{ + json_t *attributes = json_array(); + list_foreach(struct ngsi_mapping *map, &i->mapping) { + /* Build value vector */ + json_t *values; + if (cnt) { + values = json_array(); + for (int k = 0; k < cnt; k++) { + struct msg *m = &pool[(first + k) % poolsize]; + + json_array_append_new(values, json_pack("[ f, f, i ]", + time_to_double(&MSG_TS(m)), + m->data[map->index].f, + m->sequence + )); + } + } + else + values = json_string(""); + + /* Create Metadata for attribute */ + json_t *metadatas = json_array(); + json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: s+ }", + "name", "source", + "type", "string", + "value", "s2ss:", settings.name + )); + json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: i }", + "name", "index", + "type", "integer", + "value", map->index + )); + + json_t *attribute = json_pack("{ s: s, s: s, s: o, s: o }", + "name", map->name, + "type", map->type, + "value", values, + "metadatas", metadatas + ); + json_array_append_new(attributes, attribute); + } + + return json_pack("{ s: s, s: s, s: b, s: o }", + "id", i->entity_id, + "type", i->entity_type, + "isPattern", 0, + "attributes", attributes + ); +} + +static int json_entity_parse(json_t *entity, struct ngsi *i, struct msg *pool, int poolsize, int first, int cnt) +{ + int ret; + const char *id, *name, *type; + + size_t index; + json_t *attribute, *attributes; + + ret = json_unpack(entity, "{ s: s, s: s, s: o }", + "id", &id, + "type", &type, + "attributes", &attributes + ); + if (ret || !json_is_array(attributes)) + return -1; + + if (strcmp(id, i->entity_id) || strcmp(type, i->entity_type)) + return -2; + + for (int j = 0; j < cnt; j++) { + struct msg *m = &pool[(first + j) % poolsize]; + + m->version = MSG_VERSION; + m->length = json_array_size(attributes); + m->endian = MSG_ENDIAN_HOST; + } + + json_array_foreach(attributes, index, attribute) { + struct ngsi_mapping *map; + json_t *metadata, *values, *tuple; + + /* Parse JSON */ + ret = json_unpack(attribute, "{ s: s, s: s, s: o, s: o }", + "name", &name, + "type", &type, + "value", &values, + "metadatas", &metadata + ); + if (ret) + return -3; + + /* Check attribute name and type */ + map = list_lookup(&i->mapping, name); + if (!map || strcmp(map->type, type)) + return -4; + + /* Check metadata */ + if (!json_is_array(metadata)) + return -5; + + /* Check number of values */ + if (!json_is_array(values) || json_array_size(values) != cnt) + return -6; + + size_t index2; + json_array_foreach(values, index2, tuple) { + struct msg *m = &pool[(first + index2) % poolsize]; + + /* Check sample format */ + if (!json_is_array(tuple) || json_array_size(tuple) != 3) + return -7; + + char *end; + const char *value, *ts, *seq; + ret = json_unpack(tuple, "[ s, s, s ]", &ts, &value, &seq); + if (ret) + return -8; + + m->sequence = atoi(seq); + + struct timespec tss = time_from_double(strtod(ts, &end)); + if (ts == end) + return -9; + + m->ts.sec = tss.tv_sec; + m->ts.nsec = tss.tv_nsec; + + m->data[map->index].f = strtof(value, &end); + if (value == end) + return -10; + } + } + + return cnt; +} struct ngsi_response { char *data; @@ -83,10 +225,10 @@ static size_t ngsi_request_writer(void *contents, size_t size, size_t nmemb, voi return realsize; } -static int ngsi_request(CURL *handle, const char *endpoint, const char *operation, json_t *content, json_t **response) +static int ngsi_request(CURL *handle, const char *endpoint, const char *operation, json_t *request, json_t **response) { struct ngsi_response chunk = { 0 }; - char *post = json_dumps(content, JSON_INDENT(4)); + char *post = json_dumps(request, JSON_INDENT(4)); char url[128]; snprintf(url, sizeof(url), "%s/v1/%s", endpoint, operation); @@ -107,13 +249,11 @@ static int ngsi_request(CURL *handle, const char *endpoint, const char *operatio if (ret) error("HTTP request failed: %s", curl_easy_strerror(ret)); - long code; double time; curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &time); - curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &code); debug(16, "Request to context broker completed in %.4f seconds", time); - debug(17, "Response from context broker (code=%ld):\n%s", code, chunk.data); + debug(17, "Response from context broker:\n%s", chunk.data); json_error_t err; json_t *resp = json_loads(chunk.data, 0, &err); @@ -128,69 +268,7 @@ static int ngsi_request(CURL *handle, const char *endpoint, const char *operatio free(post); free(chunk.data); - return code; -} - -void ngsi_prepare_context(struct node *n, config_setting_t *mapping) -{ - struct ngsi *i = n->ngsi; - - list_init(&i->mapping, NULL); - - i->context = json_object(); - - json_t *elements = json_array(); - json_object_set_new(i->context, "contextElements", elements); - - json_t *entity = json_pack("{ s: s, s: s, s: b }", - "id", i->entity_id, - "type", i->entity_type, - "isPattern", 0 - ); - json_array_append_new(elements, entity); - - json_t *attributes = json_array(); - json_object_set_new(entity, "attributes", attributes); - - for (int j = 0; j < config_setting_length(mapping); j++) { - const char *token = config_setting_get_string_elem(mapping, j); - if (!token) - cerror(mapping, "Invalid mapping token"); - - /* Parse token */ - char aname[64], atype[64]; - if (sscanf(token, "%[^(](%[^)])", aname, atype) != 2) - cerror(mapping, "Invalid mapping token: '%s'", token); - - json_t *attribute = json_pack("{ s: s, s: s, s: [ ] }", - "name", aname, - "type", atype, - "value" - ); - json_array_append_new(attributes, attribute); - - /* Create Metadata for attribute */ - json_t *metadatas = json_array(); - json_object_set_new(attribute, "metadatas", metadatas); - - json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: s+ }", - "name", "source", - "type", "string", - "value", "s2ss:", settings.name - )); - json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: i }", - "name", "index", - "type", "integer", - "value", j - )); - json_array_append(metadatas, json_pack("{ s: s, s: s, s: s }", - "name", "timestamp", - "type", "date", - "value", "" - )); - - list_push(&i->mapping, attribute); - } + return 0; } int ngsi_init(int argc, char *argv[], struct settings *set) @@ -226,14 +304,32 @@ int ngsi_parse(config_setting_t *cfg, struct node *n) if (!config_setting_lookup_float(cfg, "timeout", &i->timeout)) i->timeout = 1; /* default value */ - - n->ngsi = i; + + if (!config_setting_lookup_float(cfg, "rate", &i->rate)) + i->rate = 5; /* default value */ config_setting_t *mapping = config_setting_get_member(cfg, "mapping"); if (!mapping || !config_setting_is_array(mapping)) cerror(cfg, "Missing mapping for node '%s", n->name); - ngsi_prepare_context(n, mapping); + list_init(&i->mapping, NULL); + for (int j = 0; j < config_setting_length(mapping); j++) { + const char *token = config_setting_get_string_elem(mapping, j); + if (!token) + cerror(mapping, "Invalid token in mapping for NGSI node '%s'", n->name); + + struct ngsi_mapping map = { + .index = j + }; + + /* Parse token */ + if (sscanf(token, "%m[^(](%m[^)])", &map.name, &map.type) != 2) + cerror(mapping, "Invalid mapping token: '%s'", token); + + list_push(&i->mapping, memdup(&map, sizeof(struct ngsi_mapping))); + } + + n->ngsi = i; return 0; } @@ -243,23 +339,41 @@ char * ngsi_print(struct node *n) struct ngsi *i = n->ngsi; char *buf = NULL; - return strcatf(&buf, "endpoint=%s, timeout=%.3f secs", - i->endpoint, i->timeout); + return strcatf(&buf, "endpoint=%s, timeout=%.3f secs, #mappings=%zu", + i->endpoint, i->timeout, list_length(&i->mapping)); } int ngsi_open(struct node *n) { - char buf[128]; struct ngsi *i = n->ngsi; - + i->curl = curl_easy_init(); i->headers = NULL; if (i->access_token) { + char buf[128]; snprintf(buf, sizeof(buf), "Auth-Token: %s", i->access_token); i->headers = curl_slist_append(i->headers, buf); } + /* Create timer */ + i->tfd = timerfd_create(CLOCK_MONOTONIC, 0); + if (i->tfd < 0) + serror("Failed to create timer"); + + /* Arm the timer with a fixed rate */ + struct itimerspec its = { + .it_interval = time_from_double(1 / i->rate), + .it_value = { 0, 1 }, + }; + + if (i->timeout > 1 / i->rate) + warn("Timeout is to large for given rate: %f", i->rate); + + int ret = timerfd_settime(i->tfd, 0, &its, NULL); + if (ret) + serror("Failed to start timer"); + i->headers = curl_slist_append(i->headers, "User-Agent: S2SS " VERSION); i->headers = curl_slist_append(i->headers, "Accept: application/json"); i->headers = curl_slist_append(i->headers, "Content-Type: application/json"); @@ -267,82 +381,96 @@ int ngsi_open(struct node *n) curl_easy_setopt(i->curl, CURLOPT_SSL_VERIFYPEER, i->ssl_verify); curl_easy_setopt(i->curl, CURLOPT_TIMEOUT_MS, i->timeout * 1e3); curl_easy_setopt(i->curl, CURLOPT_HTTPHEADER, i->headers); - + /* Create entity and atributes */ - json_object_set_new(i->context, "updateAction", json_string("APPEND")); - - return ngsi_request(i->curl, i->endpoint, "updateContext", i->context, NULL) == 200 ? 0 : -1; + json_t *request = json_pack("{ s: s, s: [ o ] }", + "updateAction", "APPEND", + "contextElements", json_entity(i, NULL, 0, 0, 0) + ); + + return ngsi_request(i->curl, i->endpoint, "updateContext", request, NULL); } int ngsi_close(struct node *n) { struct ngsi *i = n->ngsi; + int ret; - /* Delete attributes */ - json_object_set_new(i->context, "updateAction", json_string("DELETE")); - int code = ngsi_request(i->curl, i->endpoint, "updateContext", i->context, NULL) == 200 ? 0 : -1; + /* Delete complete entity (not just attributes) */ + json_t *request = json_pack("{ s: s, s: [ { s: s, s: s, s: b } ] }", + "updateAction", "DELETE", + "contextElements", + "type", i->entity_type, + "id", i->entity_id, + "isPattern", 0 + ); + + ret = ngsi_request(i->curl, i->endpoint, "updateContext", request, NULL); + json_decref(request); curl_easy_cleanup(i->curl); curl_slist_free_all(i->headers); - return code == 200 ? 0 : -1; + return ret; } int ngsi_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt) { -/* struct ngsi *i = n->ngsi; - struct msg *m = &pool[first % poolsize]; + struct ngsi *i = n->ngsi; int ret; + const char *code; + + timerfd_wait(i->tfd); + json_t *entity; json_t *response; - json_t *entities = json_array(); - json_t *query = json_pack("{ s: o }", "entities", entities); + json_t *request = json_pack("{ s: [ { s: s, s: s, s: b } ] }", + "entities", + "id", i->entity_id, + "type", i->entity_type, + "isPattern", 0 + ); - ret = ngsi_request(i->curl, i->endpoint, "queryContext", NULL, NULL); + /* Send query to broker */ + ret = ngsi_request(i->curl, i->endpoint, "queryContext", request, &response); if (ret < 0) { - warn("Failed to query data from context broker"); + warn("Failed to query data from NGSI node '%s'", n->name); return 0; } -*/ - return -1; /** @todo not yet implemented */ + + /* Parse response */ + ret = json_unpack(response, "{ s: [ { s: o, s: { s: s } } ] }", + "contextResponses", + "contextElement", &entity, + "statusCode", + "code", &code + ); + if (ret || strcmp(code, "200")) { + warn("Failed to parse response from NGSI node '%s'", n->name); + return 0; + } + + ret = json_entity_parse(entity, i, pool, poolsize, first, cnt); + if (ret < 0) { + warn("Failed to parse entity from context broker response: reason=%d", ret); + return 0; + } + + return ret; } int ngsi_write(struct node *n, struct msg *pool, int poolsize, int first, int cnt) { struct ngsi *i = n->ngsi; - /* First message */ - struct msg *fm = &pool[first % poolsize]; - - /* Update context */ - for (int j = 0; j < MIN(i->mapping.length, fm->length); j++) { - json_t *attribute = list_at(&i->mapping, j); - json_t *values = json_object_get(attribute, "value"); - json_t *metadatas = json_object_get(attribute, "metadatas"); - - /* Update timestamp */ - json_t *metadata_ts = json_lookup(metadatas, "name", "timestamp"); - json_object_set(metadata_ts, "value", json_date(&MSG_TS(fm))); - - /* Update value */ - json_array_clear(values); - for (int k = 0; k < cnt; k++) { - struct msg *m = &pool[(first + k) % poolsize]; - - double tsms = (double) m->ts.sec * 1e3 + m->ts.nsec / 1e6; - - json_array_append_new(values, json_pack("[ o, o ]", - json_real(tsms), - json_real(m->data[j].f) - )); - } - } - - json_object_set_new(i->context, "updateAction", json_string("UPDATE")); // @todo REPLACE? - json_t *response; - int code = ngsi_request(i->curl, i->endpoint, "updateContext", i->context, &response); - if (code != 200) + json_t *request = json_pack("{ s: s, s : [ o ] }", + "updateAction", "UPDATE", + "contextElements", json_entity(i, pool, poolsize, first, cnt) + ); + + int ret = ngsi_request(i->curl, i->endpoint, "updateContext", request, &response); json_decref(request); + if (ret) error("Failed to NGSI update Context request:\n%s", json_dumps(response, JSON_INDENT(4))); return 1; diff --git a/server/src/node.c b/server/src/node.c index a7b1a6f20..9e2fe00ad 100644 --- a/server/src/node.c +++ b/server/src/node.c @@ -119,8 +119,7 @@ void node_destroy(struct node *n) switch (node_type(n)) { #ifdef ENABLE_NGSI case NGSI: - json_decref(n->ngsi->context); - free(n->ngsi->context_map); + list_destroy(&n->ngsi->mapping); break; #endif #ifdef ENABLE_SOCKET From 7bde76bc4d924e9727425ed1126d749818d796b4 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 10 Feb 2016 15:24:51 +0100 Subject: [PATCH 80/81] updated link to documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2273c252..2581cb147 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This is S2SS, a gateway to forward and process simulation data between real time The docuementation for this software is available at [documentation/Mainpage](documentation/Mainpage.md). -You can access the prebuild documentation at: http://46.101.131.212/s2ss/doc/. +You can access the prebuild documentation at: http://s2ss.0l.de (User: `s2ss`, Pass: `Nie4di5e`). Alternatively, you can build the documentation yourself by calling `doxygen` in this directory. ## Contact From 9929ed65d5174f88f04fe748c39f76df9844e6bd Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 25 Feb 2016 16:53:03 +0100 Subject: [PATCH 81/81] updated LabView files from Eyke --- clients/labview/Data_to_string.vi | Bin 0 -> 21394 bytes clients/labview/I16_to_string.vi | Bin 0 -> 17945 bytes clients/labview/I32_to_string.vi | Bin 0 -> 17933 bytes clients/labview/MSG_create.vi | Bin 0 -> 15130 bytes clients/labview/MSG_interpret.vi | Bin 0 -> 20641 bytes clients/labview/SGL_to_string.vi | Bin 0 -> 17973 bytes clients/labview/Simple.UDP_v1.vi | Bin 0 -> 19829 bytes clients/labview/UNIX_Time_Simple.vi | Bin 0 -> 9236 bytes clients/labview/UNIX_Time_string.vi | Bin 0 -> 23636 bytes 9 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 clients/labview/Data_to_string.vi create mode 100644 clients/labview/I16_to_string.vi create mode 100644 clients/labview/I32_to_string.vi create mode 100644 clients/labview/MSG_create.vi create mode 100644 clients/labview/MSG_interpret.vi create mode 100644 clients/labview/SGL_to_string.vi create mode 100644 clients/labview/Simple.UDP_v1.vi create mode 100644 clients/labview/UNIX_Time_Simple.vi create mode 100644 clients/labview/UNIX_Time_string.vi diff --git a/clients/labview/Data_to_string.vi b/clients/labview/Data_to_string.vi new file mode 100644 index 0000000000000000000000000000000000000000..1ae0cf914a48655424b5fb6d6055f0b2d6ddd009 GIT binary patch literal 21394 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 VleA6W&U17cwz=UgZaQoP`9D?a8F&By literal 0 HcmV?d00001 diff --git a/clients/labview/I16_to_string.vi b/clients/labview/I16_to_string.vi new file mode 100644 index 0000000000000000000000000000000000000000..f7bd91495c47651b3a7955b83a592a44ae4f086d GIT binary patch literal 17945 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%0D%erP=B7HO=WB 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 literal 0 HcmV?d00001 diff --git a/clients/labview/MSG_create.vi b/clients/labview/MSG_create.vi new file mode 100644 index 0000000000000000000000000000000000000000..5338123286ecdd5217d36e0a7a6a9f008ae49828 GIT binary patch literal 15130 zcmeHubyQW~_U}0yy1Tm(q&d=^(%mK9l9Eb`fHVjKij-J%Nl15x(v2XE0@CpILA+nD zdhhRz@&0(@jqxU|z2^GNx#pU4?HGHlJ&%T_h7<-W2%@B;prRzHqYDD@*@8d_r63T9 z8sO!DIu0Kc2vjft4pzZ~ARu9&rVz!63SzuLcNG*8 z0301CFn!Pw(L;s=K3K zl7$cX`g*}Y-|KO(1sLBlG)J0sDMb}#JMGUQ03vvpZNQy-ATTT%fJFeo=_sh_z(5>n za}RS<4;NE+4>vn!8+I={H7$8gKtcrb9`vUe5CPpJuPVTbj=~QBTY(GuZ)QN(b_lQR z{3(WYgZp6y=FLBLc)0(oo!`YUJCMKF(UepAZ+89=!|cHSX$MbRMM2M0%g)K_j~ODl zitKL+Fk2vmYhw^t0}g};t3W2e++liWSnS?0b1|!K1hqzs_rkZcnTaMsu(^4g^vp;w zBhZZqpwVnd8Uu^sHT;dAH5Ip$o;xtiC@wW+AYGFvJoX0+NG3)cCM@ zctIfJcI6S+5O7F9APC+WVVbJ)`0B)$*0XgJGZ*hA@5p|V#PL$|z`t2xU~@;;HujAP zz47h}6+8#ojO1MrcPWmGc-*?O&Bs8Hs#G|M7Ci-xXapZM+cLGhF~ks6Kz z_5ya!SN!GotJJ9Rlso>tM3OrQH(z+ z#L=^$j|e}n_~0{l-ciTD=fOW1vz0ziB|S;;fZ;Ms4%Y}tObK@iVv495mEHJe{xt4{ zc`;6k>dOs&G+F)d!MaR(3&U2(8ES@oorN*M^io42d#J5wOukbQ_Ew>bvd-{9|Iyyl zsgrki?m51Fk2J=1xHy)PLnZ3@td8wH&v8e8`nQ+>i}i{``G`!8y3C!L7#H^KvFS(6 zNVWF((@oi*O^%b?WlIM#@-h>q&Y??6J$v@}E#}q>X^qTX=Fq2o4b)X2wnTf-VmM)< zK+5HtmZ$KDcgUMIAnhQwMUmvcRqoF?0h1A{8PJ+XP8KIu@qNTK9^yqVfp|XwxhfcvlGtWYA5t%HQC z8UPw#>_2Xx|88OQ-z|W#6@I^w|9k(|Kl=ZN=gpPgkHfDK)LlWadl7{lc(~DfXKd*` za3q%_cve4EYdDp@XCQY^K3WZB z&XSz^sXc4I${;ljtw@A;gNk^0ZF*W;GDEL=j8f?bddZ+y

p`>k$!Z7Q=B%^=T6a znOp8HV$?J`lYTsV*0ZN8eTUZ8F0-ngQqQNnc0N+mI0wa1uQAC{t$7b3Slu#@HM*T) zL-fQmXG!Zraq_YVRm&-F^cD;4^0PaVDvhU#1q~=J1m*8SPS}!K*d-_D12pNE3F0^u zLLI>mW1IabXHB!*2PqE4O*zQYQ&1)vc7h<2j%g(78|i%}6|(6Z+WQ0IraY5YVWil> zYdoY{tl78qz%0Rv36DLkFBTzIQ3mPSiCmjR>^#k-Bq`B%)oMrKvLX_`$hOV8ZAl}k zC&&$nTL{_fwVao0VdPnFp1YObm$P`xT@JZ%9$=XD9#d#vCQF(ttZlgLJ*tfIvie5o z!X!hqh&rNb%^;jH4vUYz0;sRcxG#ZgKQ}_9Ka%YoHGB-J@d`0 zCKSt67p_?rL3jIeN-dn6PvqHq;hqJ}DYQI+T#Z{C6s}RSM#<$|@zzf|-CVgEy=ekP zF?Y?^NuKbP(}j_%4bL@;4>PKS#x3WwI^`0haFTYSxkm`r_GSj~XmqAM6sld5q$?WY zkLGth!fz9K?CL#ZHFbECC+$S?JA=?Ai;vj1`DdR8AEs6?Dt`+jyj9-%q#T{YPN0Pw zYOBLkL_C%Rk5G}GYSmn^Ou(?P@Ns>M>c*o0;tHsCcp`O#{)Gbm+Z##}k2H60aZ(@q zF}U8seBYcMm!XN9%#UwQBUhuvGNU9A7c;uDYZjzrp;yG`JjN(PNbXn%A!E>N{ASP$ zWlUE*c+J`MZrruDZTstkqFRctw<}dVS|p%ms17-_(aqcCJX7+fuKAVCvQLRts0cCL zaj@K(nv`^jxM#}H4@V`Z-QBmH8grE>-1N3y%H|vK#^+FQ(A`*i4ORDARx}kB7EODq z?<{7=yqp)|=fjoW_x(^?@|Li|>8bO>VdYJ{t+r#EZTAr}I1HB@Tj{CC^&5|oYUbxv ztJo}u9SC*_g>|Q_Ux-Q)B)OtUZeZ{aG`y&c|}`E zhD?^4;<38QZq!5Qjz+=;^AYNoh7p<5rOKyv9t)INeQ3A25mIvS;yAXyXNXij^GJlSiUNG0VEKc->o=n5d$_GMiW7QS@h+W*wgJjQU_SJ%)EF zZ3>ABaAN3J&>DYYdm1c3GAHTiO!8KUXmh36Y=(Su69c}7C#KZ#3l-(M1h3_R4pcGT z@B~Hg&e}$|;#;bKq&|&drVmJYaAzBx#GphYB5iCK-au=XG!iE$O+H zwGs%-Led79Iy*5wAvnvAi6NrX2<+X8TYf2y<@9Ml6`e#Bp_}uGN+K9Mx$IE}(soQ4 z_CpZf?29uCeXmmP>1c{zJSzh_la;MN5xY-N55ftG#YaIrsTjiU4?Dr%Iu)Y4QBTZA zvD1xAViPOmb|Q{Wp7U|jWMY&_M@RMD+h-=ZpBOFfnlITqNeaT5@%6fUW40<)_Z&Qb z8>~un1})q74YJ$BYZ{(gqzXkVR!sdIXQ^go@bZb@x!`u>iSn$-yCtOX7qz?GIey2F zKMl`?>zpx`?jKHXjl+lB)fS&pmrnA$=$i1>s;0Q`OpJ7k)a~Y$vbP$SD<|o zq{Thtdhze&xDnOUD08VGAwn#n#SrsY%^IolCP(~GK^6M>m+uotu6^Fp)3=v;}uof@`ydfy2wI?$tB zSPC|(dK$~xO;VMzfnQj*8d~F7XVmLCo8g*gq}%NIA>()|fZl|XwZEug)j+@D6@9~f zeg1~L+zyTt z67Jjp`Hc0f*ZRM8zUvd;gH?@wTM4SztK!L3c4@@*=1B_YD_4Q0&-U`YMZsq!y(g+c z{&2f8Iwjxb#C3w}@De=GI?szgJBrud|MVbSkpvoKAIG+81<62+trKHL*SKf2Uxe5nWgr1Ha6~($=`j%1=f%5{gNw; z<9O5GKp;*!ZsM)DBFS7M!uSNj5PceXQ?ChnDUy;e9Sc@RF@Yth$BLG~Z4-e<#FZJI z#he|9N4fYEUvfPgEs(FUX#=@fh@`RClGpqbu5|(OQ}}eca9EqX8H(7$XKS96&F)rvO6@LlyxB8-^?c3<7{a zV3UChAP{bVL5Csw00WB>5&@G?uvY?S2Grqz4Cn*b0N|lOp$5t|T}1;7a1{(tU%3Zs z!yE_dD-VDgya%-5fC8H{z>lj?;Q<^rQNZ2?_&rbqD{DAApay)o@)H@rYk@YbhRq`i zAcxH!Do|kO@yALWU;qO8zzWRQD>()X{Eaw&--qch)(&Nu87Y`QSTE8HsAJJ#lsn|( zj2P2w5}Ik1Du&q#gzd)!0uI2+^UE`E97qfN#97NqX%U0 zfDSoO2*F9%UxS_r;hHDW6Hz?B{AM1i@HHryn-ClnN(>J42GIaE&4B`|e=lIzct7@n z;IFPd82=v$^k3=UH?ICvg~|V7`d^Ay^5b9V@R;qGZT>g;FBY!kfYEFE(f{(x@)tk{ zc04>|J!AVrzI?e1$YIArHh|`Tb_aX|{5$>vNWK8wf#V_T8S5YYe+mJW!!zVR?f*0m z4DnYv%>N%QUjEfSOnyyQ{{80v;jhHT783HC92U|q6oL>E@-x7z_}I>_q*q5eI=T=9 zgnwyV$F#qa$= zpdG*+x~u=)ot@p^#|MPFFa9U9oArt1)uayooinZ0p?@ z?8dOF4SQf! zY6oPnowRX;<4p_B@Fx*tQwZcAwMN(~dXKfNJ-w=?vo()<#!0c7s4grc`zkqF5{W{L zk&ekww`=Ds-SjeilAlH2C(JRB5iRKyICP|W>l=cW?hT>Al+?i6E{+N3CVcE~>ZpgW%)oBAsEy+$JKE=|Mqj}qFGxYV_G z2x$033HY+t=G2lUb2tO@`H}-~7|xHESlH1B;o0Xq`x97&f|Dlccw=Za!??})L^Hc1 zv=Fy8hqkKM<(|=fz;uAH83GG_Ri* z(bXssK7qU2C^NU5FEuqNxY4;s=h+-l-1{LZH|n;QIdqhGj5j^8_Upzs_~?1v@>KuO z4F$^i{#3W7WHa_^8D1jjSRneEWjVflcn|k7oADx=V}xUG=fkA=ZX>P^M?RCTa)U>o z42lEsq>YwqS7$q9zJI4E9?M}RoM@8Vov@=V3b=T;u;&myP-H++c~VZnTDFH=wuf56 zgR{k}DP(JIv}u;=SeUv6uqS0Vz8)dRl#c15cz}cDy3mH2JP3LHUh< zlQn@3qfAiN3-JgC<|Cpiob~FtG&Sn8n;fXtkHH9^oaa87m^LQrKhcmp+HIc~KRSKf z6to-rwJm5@J)R%?;t{_*U1I_sm|~W=LuzCbH!{cy<;4&gE5fsEP_r6R2McQVep|(g zbWo_u&1yva3{~WjIxaMnbX!zIhVZSz4D%K)f=UWP%9e0dN2eiqO+UHV{7PY(>By$^ zymp`06(KdOIiI`KVxFoz6MppcZmz&6clbLTDieed6TrV?ZN8If^|%cQyU!m9MqHI)`1TkV1qmT?nK+eM~!m11C~OhPUNlCi}q z#}!!{Uadh80$s;$Jd$b5$|jvJ0|O(im)tLSt6GjhLgrJ<+`8)4 zLQ7sXJBCApDl>!3%PMEn1c`l{F`d@MwMqiLiK4{{@3X4R@)VTw$?dcmKW=D$3e;}T zmQn1b(wPRgRefIQw7AWW`wa!@JWT1U`!=GF-N__k1Q8U zDR+~9#l_zexSY_c7G`P>7qrOtD$QSo`?O3JOWuah&${)l%`dSE!^SUhB0SGP7B$)_ zHOu?;3xku>(~b%Z#Y8ucBtvso#Qq^i#{LbrMgQO!ccI0Mz}s>@55pa}o{l%yb6D?A5dZ$F4gWKrb&^m=uv_w`CeFRwTN&W}$WY6s6F2x#?{^Xv=sLEb z+E+|AZ`fr%5^8$}7xGSGuJO~f-p0P8ZC}SoG0#n}bIXF=^Qe@4LuP~udJ3zH%OLO z`-)U|KUQc_la_2I9)CbBm{Qe+d3n!1H9qd{{yT=k7g&lIC?y{b4P4e`k}9S;k6Se- zee#V~{oRpQS#2_kGE6a4(*)+K_O@R*bd*2D6ky_BJqPE>m?}6xC5~0TZtkSrnV2i{ znV75E551{SBK9CKzjp)8Px9j6Lvae8Dz-bpSEP8XVap+MmW=?BCTxZ0&j+Q8R8bBP zn++y!C<&`it9G<_2*s08=@ozYf;HliXjU3}9TE+ch+pSMOhRboz0pqmFttcyH!DJq0r z1Z}2y-$wWOUD$ev_Dh8w^P?6X22ehTos+X4d!sqOAJt-;sEibaF#+LS0w8jeGw!Snmjjg@sPA}$? zX}0`s(cC5ai4ciP=A*iUbjH~m7rabQbDMV(blquHFTM7=tZ@tdobGW{Mz_LQDeIik zb%l9V(8su~p$6P5X{Kr~gBBvidQp-ff@(1~T+_*{Iy6njGp~>QzO2}0mkOlf_BWyA z?A^JC>dw!1OQHXj09xi~bhw&Nw7-F8GARq6n5o}m`!NnQtWpPB??<)I{Q`Gxl|V20 z>q2W@f>IUvUaM7fS17IJ&xFArA_e{vkAGpU|BWjS@T68=EvfMH&-1sVJnXFRd!OxPTZ(O42vH zQFvy1T;SRh9~JWE#3FO`=6Oij_Zg_(z5W?Ko6k$eLG`CdQ}^p~H=1}6A68h*NJlmi zpj;r777*$+y<5UZ6;G$^;t>FkM}ndso8PLS1|=iBh&$zyOlpp3>Jzn;?`&xHM%M6! zBF)7226*WQ$NE@&u8dQK@W^E zqcgJhD28_Qh~qBK7lUoX@EP1d8(J~ghU>fMxAHiiZ|$3mZ(<1F=e_pcr^WcQ(k_)G zw|lQQ#q;)wp3XaH;Z$5qvf-%8Tf+ax3iEy+=3*T zkr$*>l+pu%7+F3@Nuq|@QU$r|ppyL8I+uEed^v(}7`r^R~q!1+KC2Z4zBy)!EG{O%HbB)ROLNbi}YZ=4mDr?qu% zc9usDiZg5MVJ>R>hCL|G0LHMavt0tc5dVrrjo6+>0uh791ck? z%6M9OWw%~WZUsIouBYJO4Xjt`Tle}WIwtN);qP*1Y>FJ-`_M5lcN{RsF1|qjF7A7i zCZXCEtx0sbxI*rzKDM#^1yV}|ZAQ9n69bP7jlm<$P}R?dmao%>#SKGE?QgmfU8qER zH+@AV7-vG{@{BjYe>M7@mU@Kt9;1fU@!rA6)AyPog*QH4U}ZDE9eTzPP~OB=`AqY< zawTqtQc4+@fy_xD{p|>n^&5TX7tQZ6SX0X1>Q~%pX+OB>Kthq0Kmn4T#$Jz0^I~E(_X^aa9rBs+?3vr2S^7Uh%ymD5{2m=J- zQRCy@&^9mC8HMoQCd{ zsMC&qfL7x4AB?10mXvWx-8_ksUdOb)VVGJ&t%SqNCujMZ$aEhMVX9Ily9;;x&bmTo zpY-c}uRI5TW6SnfXl0?@?D#k2SEPdX?qoKArB*+ElAj}dup@ezj;Gj~ecGp$t0T76 z)+GAAm>wIcN2PV4=rm7HE*GEqqURw}QNunG;d%0X-~1uuc5coh^fmRUQ65=;tqHG) z`!Qt5@dF1Tku_5mH26j>l6veV*rf1iE~*2PdXMf^&OpTI^}HWWylwVOMLzU@qQq-X zrO>EO_f}Ae&C3><3^9+!u|}IWl*t(fW36ul``LRg(&hn|)bPO=-hC8eH0(HTxD^7N zsO8;~bdINH@yB4=1P)~KTWT5(VW3X^1`9R&}ytXX=bNB#Bj zsL1G&W&L@D5LO_gLMW+G>MD5=WvUDsB~bI1quruuN;qmwRVnA9RH|GKf@R!P@7RJO zD$$6dD@!li2^&FZ(RHRvpA|fHCp2?6?Py&_X+#(@QVeq;RUdDOn=CESSZ781kfVus z%ZpAy2^%janX&9B-hOf$boq!m@x|~hB}$pau3XoOx#)*B#~2(a*vY<^F*MqZVTb!- zdSQm-sm0;Y%%DRk58hX7X5&4rrw&sdL}#pm69>-%xViNmzO7%#D^4_rMSCJe<~Zdp zk~+~L(E}x{GWq2g^jpmU6MO?r`MX8I8rl1M6zil)A@7Uf47@qwn>?J}nwodT-yi;t zv%Nb5wxg39QJ8`o%?Wp5aa!I2=Q%Z1B~%XfDUu1K=uDC#B|?V<;>vGQDNV}jK1DSo z!LE+eHQy=uG6XM_mk}O0Rp;qhIo-G>jw9Y*`jIxqwhld*DjS=bVtgbJt z;C)?8UtuYke%?RtF?Y}`?CgRi#>izE6aZojAZ0v1!G>F7-Zbz_<3f8kZS0!z2 za(->)HZq@%Z=~ZBalvW6=c~@5%#YS+^W=E_@II~f4QuuMI?Y4UJ5xt&_QALO#>)b0 z=oYJ(#>*xi&?$MJ(cbn=dZQdRNun*%#$NoT?&5}miJU|?Q`vy}$7wm*7*^@Y1P_XN z#v*xngDslm1>Wuv{Y2W^bkWFUn-H02=FN<{J4V5R~R2y_` z;l~Q}XNupI04e#qUFLAzKjszb6cnbCx}BA$@_i#f|atENzIo}Am7Z8 zLMvO0p)fG7kA`ewAEqy^6cOr+6enFIN7cK7_qaXKhdvLZyttP*v#;*fiEOi{lkhwa zy|QALrs09ZQFV;VckBBiO#(czLXK?a7CH=MJVM94=%V*GGxBKq^@C}w?Q5!ua76jw zxT5#Y*oI>DcprO-IF*dm#=6_!y@KN&;eh%(+@4RCh*U;DzLSzl(@@Rt7eZ-MT*f4< zVnIn1XL7=+Z5+#U>Qg?jXL$Z4i?Rx9nX7E1hii{`&l}y{f1rn}jJ3WaUqZy0qSHbr z?}>#@*zOUd@20D;^<%{y*TpQ##5EDxrQ6;y8LsaMDePJ|#wCUbmkyEG3|_yK+}u7$ zuuf1GjkkwxA93FzHOX&vvVP9&SbJaGnhjmWRGr^O+x^bR6TRb*H=kT=?Tm%kp?*%; zc|J~IL=N#ADyq4%(#6F#3ff#qw49_j3Ju58Zn14o>0M-33qij&%{EF#F+Pk!*Rtlu zLUTgbdRrJ_<<;2U;WYaobo$xSH>$IXrEfyR2PkVN)uT`5HQh;Sx|4l_n8RPS=-t}C z{1zP3AF6m6?-Ok_cxmsLpPjGDK2=x{rTP}Lj8O%mDlI!d5+rAs_f$mb41Jq{V^IQ| zUc-JQv0%F$E7EN?=iN7#?RX;wj^QbzSR&^VxulQIX_dZh~cORdd}*li9EQ{WYAUzE>gEFmua#3{QV@ zyJUbf-eMar&LIxY1rh6r#MkXv-8U1Mp+9h}<8L9UEM7hmNcJJKO0-+~kft%__3=>d zaJNLej`8l9m33g`%X10aDC#@yNCn%anG>-d1uTE_~id{golxvBT*}{o? zbDnXZ{-ED5cg6{4D~5dhiyC!tuh_}mAqp$)+lKo~r=P7;3y5k()n4>H!;c)yGd_HU zy?y%Vn~;_CH{$}`^#hSM`-Mxe`?LH-;581G3jq6O4|}hM5j;o$^ds9Dgbw>^!VDDP z8`YH<3R3_G9t?pCgQJB<`_a5QA^@+*K)wK?pm#>H)`SM0I<|XRlwYb+?X? zNc#IW96Tf5jCD_Mnk0(8pV)+lK?QRyE_=x%!^@#o`8T#|@u>Q|mhRPt*|tqta5)M} z!?TXnO|v!a&ms?i_>NFa8R9tfX9<*ME7z@63oSgP z>#vj8yKP)#F+nZCo45M-tK#1`M1$8 z)F%XVi{TcRv??<==_eOCb&k(H%jt~d&NLu>gQ$PQSihM#8R^lm(RX1Y_KweKLTtMv z19F5E74T#)pA!_6;bzA3^hdTE7!s1cXr;Nm6h+!Il3;G#QdiLKYC;hgRUOTGW+D@| zbE9xUF#mC~z$s$ARH}L52%;{*2fJb`Xmm{9>v1;c1?a#RJr* z*h)WTtasv>+;=dFy)WE>7PGpRN%ykRJ4SK5hcuG+@wal^TEj)QsN`MQc1FKGR&tKx zn>-piLo%dA=5^V+OWbp4uUAtfxue?V>gmP=fjhzq$ijgGR*kS5M;?TAb^l<2#z7x} zw)$^vgWuW~fk3)XAdp4{(?`~{vT?DZ)>inF0doSzH2&w`SpiyCL=7ByfwZ4opk`Fk z(fsjc8w4KL(9)E<(g1-srKB`}eBlM$S5okTTJ` zz4}cAj8{`quyntIL2#Bj3R2S7ytkZ`w8AwX?PcW#(*Y&~E=yDDrwL;(!Q3 zEUTt<-50_prKWuMM_-7zq^yL}HLsBW zkZJ`Wm}|b*&cc!ZnxB_alj6SS_mvc^ug8T)mey2IhIItoCI_+r?)=b!ca&Arl)2^u zrBr^-8+?+En)LNt!51hg+_AjYsRMQ;e)b)dl>S%WWgP`A*fSR(_#nsBh0=n|Q z;zFR7SNLfkfmcUL>)JkoysVal zx`uvtoB&@r}Aph7S`Z>?PoFn4EYdu){)AhARVs?%FGA`-$J^Lf^ z?T4I^_fI`W;lE(l*ZjZr)%#PAS^Q6oEA%f|^k1-~zhFP({Ur{rzsw)k;C0`?f98O9 V8irvgYs#5gxLKKdSOLEe{a>{HI4l4F literal 0 HcmV?d00001 diff --git a/clients/labview/MSG_interpret.vi b/clients/labview/MSG_interpret.vi new file mode 100644 index 0000000000000000000000000000000000000000..7ff08636da9f17c0a8c12df7be42849bd485cff2 GIT binary patch literal 20641 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 QgLmWp=wQ?zKc5W#0oG!v`2YX_ literal 0 HcmV?d00001 diff --git a/clients/labview/SGL_to_string.vi b/clients/labview/SGL_to_string.vi new file mode 100644 index 0000000000000000000000000000000000000000..3d6399b3935d905b1a0ff0a344d465822ff06ba6 GIT binary patch literal 17973 zcmeHP1zZ&0+McC57ik1RO6ia=mIi5-E|+cz0V!z_FhD^h6%Z5wQBV;Dkq`t$LJ*O# zXr&}1B)*y56}iCQ>%I58_xt_s$E#=NInNvC%saEQ=ggi#52>d>P6FU+7^-P$$Qv2~ z0HPNF@J#>!U zM#bg+c`vv<$diGE3esmi(&GLO-=~-^$52(SU~s;J_1wESpagF;lsI{8K|UC323(Jf z&FJiOQI6;^G+Pm+mW6@7*mkIn1<*e;RI?fJwI1?8ndTIMDm*AQC}aQ(IvO;_1-Ce0 z{~%ETi>qI{O}rh-8C6yVJUfSI>Zro87n24!k|}H1Jt^4jJpt<|0^##q6oxIcVOe2( zu;>uLlK@XF2(W=sL=gb?dTfMHtTzAvTlPzL(~+g95hgoz3pbZWHTp<2uCNC{5XBaH z5m(rI06~V4b1dYy4)*H{@akGxI=^jcfwjs%(L-Dqpyue+o;IhoGp^w773v=`rJ|U+ zsocc4c%s1qbt>1SYb0#VuTVF-AnJJwi`THotBVzleJrA!+6*5?4*082h*Y+($)6g$ zPgbh$u5tfsg*ox<$=drF5hqQW(hF`R9g2fxu|?ge6K#l)qQY-FQ+Zb1#R~DI(Dr%pZORNNM<`_Zhjn!j<6H zR@!}wEV`1+r&KBO?vCIyeYD<7bklNlRKo9u(oqo{e0dHfrn;Q^D7b0KLzY@Uwq$na z1>5MD;F+{*10Pi>HB=F8+V!e6gp%1DZ75oe&UyQxA|uC(arJ7x9|C8FR)$v6-j%x~ z#;>##S|DAyp0yGYCwP_&kB2>(q=}DV8Q*p5f|gQ$-KES498ov(GsD6km+`fchdA87 zM5g1dlKLg#uDP(HfT_u;UeVkNicYuj6O)SmFHHLCR5FrfrJo=EE{^y%t0SF)K)tV` z(UKn2frqA|RKu^zg%AoK?7xqH;cdiMnLUNIpIH-qBKu|-yOO%zMdWqCeJIJPdU_%N zX5TDjtqSfk1`ms}r8Ms!OXP08R)>iCZT4BoTp+F2t0KN8qjAU4;d&JtYjHTB;Z>^I zXz`<9g6s={oGs(?LQm=7NlBX402{>0345Oyox8+qiF`HbCGCpQxT{$ybP=n^k&%0* z?^WB5TDT(j^~m6{4V4FW)eoJTh*lbFAFbng{qn-ijF?#(frdURPl2fWWAu#8Q#p<0 z)`jt5sZ#^aNoN_#_K@uBL>0Zhem>mwIbwR0Eh*KjC!=G0ObNID1dl{dIZ5FMoYsUME*nQ<^j8#5oO>po>oQ2&>m!-HQh~n{aiaoun@ugt;t;Oj4CZT}6ER~n1Ylz@c zb21l~QyxTpOs!TrYvRakSlXe-FtfY;&C%jBbFl^WWJOU9Cmy_3Y^j+J5)3Qa)has~ zn_&@2!12DOnM;XHZ)(@@=kUh7r+Cznr>kDhEI+AHchX)xBJ1_t(tx31(&N+~ zNe6pWmkq3aIhvvd_t#JK_cc#EzLLEns}7$3j&JyGXDcX&n!R@?`%2~r61d~z5XIft z-Bddtfv{wFcdtuO?d2M8JThur1hq1cTVWDo#uIq_rTwz}{cq!1mrm*}DCb1ZiIQ~# zge)fymCaMc-bvPZ%TO3TYWUE0DbVwKXk%37>+2M^Z=G&%d*_{>i=ushsHdDmkMZf5 zB1`4U7}{O9fwAYub`@V4T$#;%7DMgN5IjU~dC}tjy=ne zmBu0fX)U+W80Ay~o>+5Ct7mLSgVgOzpN|m?nz4gVwEQ@Gd8{U7%cnSp93+O=d#E9=Pb$cH0#$ z4?1^!%MNTD6Y{AcnWvEHCA(lc!OOzYQ+tV}jBSv|G>d$C_vf@Z=ZjuP?|o4!y)QW? zG9LaIZrfv?z4z1EnsSf!0Tt~%ReV9UiFE;dp{x>(fX`ogG2{YA^fwz# zPPSG&Tj4DACBD|-uvK6dKeIo6`4!??VS;ys))%okuN5=I5m-9+LR}l-^kafTP0~li z-lPrMat?)fOoWf|^3-!Jblc1jHi+UV@_bV95@7m+x+PCfqo62)ppi;iHL8(_@$|8G zCSI3aua}RSEHSQ}Usan#9)gp+B0nVaeywI?OzVmMKBLv$%eeVEZs*XqX+RU)6@cGu zD3q|hEecge#FczMIds3WJr!TeITR=H33lR_?8PQw-69%Di)A-H3US-3Dq$%Dd4bBA^;!|5f>B|6v1$0n6UsJ#6iqIYXgVAk|~Rt zjH~BPiZGb%D{PR4CxR3USTNt#AlOE5fZb@80Qyo#k3Imv0Az^4Vh$EO$k`Dr^dNxU z009;F6$bkV0ul&(1_2?c!tn(G83e%520#vhC=gIU07@w(SRi2l3qBfwexQy9EJ0w2 z1UYC7M1j>oJE&REAKeddk8TT!2Q>wD72Oip2XuUBtO5ti&oK)Kq4?-JIaJvs!kfoP zmT$=ohLRk#-IIAy=GPLR;9KYV;45eK4eh(Q#; z=mAK_1_~Gf+HUnn>mf6=%_cpD#VEJaV?zHX{(8r;U{n0f>Mi}-Z1A7(r>Ca}n?-NY zXk#D1_Awl|#r)r>g{C`y~iKEi*z>r##!p@S-NLDGkrC4v^2pqmm*7hZ8D z2JTH|aC^^w9!vR{ynxd&1F2=QdEIbg-f7|e0s?k&g19-tVlq^Sf`)gdZHY;ja2$ zxqJ6g>?saPOj%@qYJIptEs0VtgT7k$4h!w;A7hcKk~*jI4^5oRqrFIc%l*r=2A9x* zG?_(#oOh?M3`R!89Prb{HOQVLZ>Ia6C(S!x1wfi;DRNN?;O62IsDIz?#tDq!XJ9|XTE1Q~C!hxA{RHaZsse19Fk75a|+SEZ# ziUjE|ZftI@xWC9`j5*(Tgeff~a&{SsJ84%;kT3)P&d7c`rQ6EfqGCIX>CJxnf zB)YNf-i-MCd&PO>6E7+mEyFy%CC{7L1XoAWl7MR5vY?oD!+LNW5e>1uk> z_NtNhDfQ6{YX{n^dh*%>45$a1W6yrNbg(h~^#c z1*Vg30cKx|)ZM?1G$hn~b@MqF^|JW}dz>)JiF>*Ji^fn4_xJ2olZ+-B5HOaQZ!<9xAF_Z^_THZ__Sj<=uN;zOwafFlZ12b+-(()rCSGky~hf{MQ zxcG&0jL=OM8e7VP1suZz(DS!su>2fvg7;#1#b5Vg+>_uqf7~xU!@$CoOc#E5cw%f; z>upQl7hi#keRj$XH#lg7Y8A{lsTCDCS5F)3C+^Zz5?2!Ar&HRa7%w>*F~#+5MAwWf zA)4oXVt`|Gq$(3lN>SkC@|b?!vxB|8DT|der-su5?SYOsSHYA>L3*OJk7 zxRf@ zpy^a$$UHAjD;#i^B9ZtC`MuZOEkW|(w0hy0PwRMxV+ZfXzrHE!m`ogZ@$=2Tf!<5?M7KB^|gF;HxNh zNI=Z4y_2@`#Eg@|-luPn>EvAkZ|&V^nLjh~>e+^o(oM6|)u`}Y;FH1ok~76ZPr+!f zH+xm$+mBa*EN!exd9Zrj=KFs0iqEwzdPg6rsryLklcpbT*_X0&wy)-I2}hp*c218 z+yJG-!aMrbrm@h7M<`)ZNRcRH?O;jBu6J=$6Sk-LUY+hYcfN3#$yD1bO{R&mFod@A zbTZHAOsa+B8x`hrtf(=(uPSxS{rj^Rm);8(?3#b(*nTgmtkBEP&)Kisj<$f=zvX1C zFCB`?s2bThdD&CDmG`N7u8?TW(rCGUQ?>uy>FlWBV@nE3K~;1u>Pi(t@B=8qi5j!Y zJ%^X?eSROp|9#oObAJ={2jV)5*7GV>!aB=S4oWje;De!IA&mSlU^4_~^&1}`izHc( z>IEz7JBf^cc9`rpSXEAtyp787ur)VWARD*o1e}AFo#-c$KE1EHgro`fJg96{Ac6R7 zH#SaGYGNITFL^{%tH0*!3yl{{j>c>>Q=Tb^BEL0K#-MSjtb^W+YR47hS-qZ6DJ5yf zyHML+F%Jw@aPDAMSmAmd^XV}24-Gy~97!Uim2;Chl1V*oG%mOv2y z2ZaFX?d5db!4{fM{eE8a06Yn+U>W(dc})nghWWpK?lv)-wCiSn0=D|DGh4x*1l($a zi34sivL1Hbax1g0`g5=z z!0)v?*U$fl{9w-VziA64FckcWnvry$$wy2 zH*aI--A{8}#MAD&r#okBa`J0-iP55Ky+>Nb=)3D1B0n{K?R_K$lp%P^mOr&us7HO! zO;yTTyPUhbHZ0CwCo1qm%hGDUhj??Sq&+W8IFBWhYz4NT#60p`Q>J5H%`*vdTBI|x z6oS$RhnmlmH>qCoVuhgCQ5j1L*Sv)j08euMfb>gNU7G9Evkk}O=p-a4Y`*WVif2Z= zv2q2^zBB)HsGhhjx-_hP(DK{KJ4UmqW2sMJMz5>*=a@{5pA)>3G;wgL zI89MHD$;toa54TS>nF0|)MIRMk!(-g?7dC+9H*zNb<<6Cn@}JU2!-Cy1MP{Bb`I`#>;`IE0SsE-sQ35(wgG5`7Itt8 zuD`@r1^0V343Tx;{-prh%5@x>LFSC-SjVpS~zwVyWT$Y0n)UTd9?R8i3R88ca4Q3w3H50Ky% zs(}ZC#Z^u=u2NXsp`fE6ip3)uYW6?dT?XIivBvOtRLVL?B`oGr(E6DxJSjsRMQncX zG&Iz#ZLu~ea0U8LUvGKEb>C=1HGL%3CS6eK|bamcrY> zEnqpqUmOwmND3`P8#w4)T1bdDaKac0AlbknupIhFPYfo)UgsP0LIghU!pH{SbK7v+ z}2C?xrb{^Ysk=t-(TR9Twy_+~MHPa+PX7a+01yQL literal 0 HcmV?d00001 diff --git a/clients/labview/Simple.UDP_v1.vi b/clients/labview/Simple.UDP_v1.vi new file mode 100644 index 0000000000000000000000000000000000000000..60ae4707721f6cce5da3a3e12d3af4d26537d0e6 GIT binary patch literal 19829 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 ATmS$7 literal 0 HcmV?d00001 diff --git a/clients/labview/UNIX_Time_Simple.vi b/clients/labview/UNIX_Time_Simple.vi new file mode 100644 index 0000000000000000000000000000000000000000..f807592167ed8582bf1b898b0f849eb8c6af76cc GIT binary patch literal 9236 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 XaUTOM^uh0U7APlI4~(6VkJJAE6G&oH literal 0 HcmV?d00001 diff --git a/clients/labview/UNIX_Time_string.vi b/clients/labview/UNIX_Time_string.vi new file mode 100644 index 0000000000000000000000000000000000000000..b9c05e74e28de7fbfea5bb867d2d7ff8778cc2fd GIT binary patch literal 23636 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