1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00

Merge branch 'develop' into feature-mpmc-queue

This commit is contained in:
Steffen Vogel 2016-10-30 21:41:23 -04:00
commit 3e93dfe17c
24 changed files with 288 additions and 106 deletions

View file

@ -19,7 +19,8 @@ docker-image:
stage: prepare
# Must match the docker version on the build machine!
before_script:
- git submodule update --init --recursive
- git submodule sync --recursive
- git submodule update --recursive --init
- docker info
script:
- docker build -t $DOCKER_REGISTRY/$DOCKER_IMAGE .
@ -78,8 +79,7 @@ unit:
dependencies:
- build
script:
- make tests
- build/release/testsuite
- make run-tests
image: $DOCKER_REGISTRY/$DOCKER_IMAGE
tags:
- docker

2
.gitmodules vendored
View file

@ -9,7 +9,7 @@
url = https://github.com/warmcat/libwebsockets
[submodule "thirdparty/criterion"]
path = thirdparty/criterion
url = https://github.com/stv0g/Criterion
url = https://github.com/Snaipe/Criterion
[submodule "thirdparty/libnl"]
path = thirdparty/libnl
url = https://github.com/thom311/libnl.git

View file

@ -24,9 +24,6 @@
#define DEFAULT_VALUES 64
#define DEFAULT_QUEUELEN 1024
/** Whether or not to send / receive timestamp & sequence number as first values of payload */
#define GTNET_SKT_HEADER 1
/** Width of log output in characters */
#define LOG_WIDTH 132

View file

@ -26,16 +26,24 @@ nodes = {
### 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)
layer = "udp", # Layer can be one of:
# udp Send / receive UDP packets
# ip Send / receive IP packets
# eth Send / receive raw Ethernet frames (IEEE802.3)
header = "gtnet-skt:fake", # Header can be one of:
# default | villas Use VILLASnode protocol (see struct msg) (default)
# none | gtnet-skt Use no header, send raw data as used by RTDS GTNETv2-SKT
# fake | gtnet-skt:fake Same as 'none', but use first three data values as
# sequence, seconds & nanoseconds timestamp
# In this mode values are uint32_t not floats!
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).
vectorize = 30 # Receive and sent 30 samples per message (combining).
},
ethernet_node = {
type = "socket", # See above.
@ -141,7 +149,7 @@ paths = (
hook = "print", # Register custom hook funktion (see src/hooks.c)
poolsize = 30 # The amount of samples which are kept in a circular buffer.
# This number must be larger than the 'combine' settings of all
# This number must be larger than the 'vectorize' settings of all
# associated input and output nodes!
},
{

View file

@ -32,8 +32,8 @@ nodes = {
node1 = {
type = "socket",
layer = "udp",
local = "192.168.88.128:12002", # Local ip:port, use '*' for random port
remote = "192.168.88.129:12001",
local = "134.130.169.31:12002", # Local ip:port, use '*' for random port
remote = "134.130.169.98:12001",
header = "gtnet-skt", # 'gtnet-skt' or 'villas'. If not provided, 'villas' header will be used
vectorize = 1, # Number of samples to fetch per iteration from the socket
netem = {

View file

@ -22,6 +22,7 @@
#define _HOOKS_H_
#include <time.h>
#include <string.h>
#include "queue.h"
#include "list.h"

View file

@ -8,10 +8,13 @@
*/
#include <stddef.h>
#include <stdint.h>
#ifndef _MEMORY_H_
#define _MEMORY_H_
#define HUGEPAGESIZE (1 << 21)
typedef void *(*memzone_allocator_t)(size_t len);
typedef int (*memzone_deallocator_t)(void *ptr, size_t len);

View file

@ -47,6 +47,8 @@ struct node
int vectorize; /**< Number of messages to send / recv at once (scatter / gather) */
int affinity; /**< CPU Affinity of this node */
unsigned long sequence; /**< This is a counter of received samples, in case the node-type does not generate sequence numbers itself. */
enum node_state {
NODE_INVALID, /**< This node object is not in a valid state. */

View file

@ -29,8 +29,9 @@ enum socket_layer {
};
enum socket_header {
SOCKET_HEADER_DEFAULT, /**< Default header in the payload, (see msg_format.h) */
SOCKET_HEADER_GTNET_SKT /**< No header in the payload, same as HDR_NONE*/
SOCKET_HEADER_DEFAULT, /**> Default header in the payload, (see msg_format.h) */
SOCKET_HEADER_NONE, /**> No header in the payload, same as HDR_NONE*/
SOCKET_HEADER_FAKE /**> Same as SOCKET_HEADER_NONE but using the first three data values as: sequence, seconds & nano-seconds. */
};
union sockaddr_union {
@ -41,29 +42,19 @@ union sockaddr_union {
};
struct socket {
/** The socket descriptor */
int sd;
/** Socket mark for netem, routing and filtering */
int mark;
int sd; /**> The socket descriptor */
int mark; /**> Socket mark for netem, routing and filtering */
/** The OSI / IP layer which should be used for this socket */
enum socket_layer layer;
enum socket_layer layer; /**> The OSI / IP layer which should be used for this socket */
enum socket_header header; /**> Payload header type */
/** Payload header type */
enum socket_header header;
union sockaddr_union local; /**> Local address of the socket */
union sockaddr_union remote; /**> Remote address of the socket */
/** Local address of the socket */
union sockaddr_union local;
/** Remote address of the socket */
union sockaddr_union remote;
struct rtnl_qdisc *tc_qdisc; /**> libnl3: Network emulator queuing discipline */
struct rtnl_cls *tc_classifier; /**> libnl3: Firewall mark classifier */
/** libnl3: Network emulator queuing discipline */
struct rtnl_qdisc *tc_qdisc;
/** libnl3: Firewall mark classifier */
struct rtnl_cls *tc_classifier;
/* Linked list _per_interface_ */
struct socket *next;
struct socket *next; /* Linked list _per_interface_ */
};

View file

@ -59,7 +59,10 @@
#define CEIL(x, y) (((x) + (y) - 1) / (y))
/** Get nearest up-rounded power of 2 */
#define LOG2_CEIL(x) (1 << log2i((x) - 1) + 1)
#define LOG2_CEIL(x) (1 << (log2i((x) - 1) + 1))
/** Check if the number is a power of 2 */
#define IS_POW2(x) (((x) != 0) && !((x) & ((x) - 1)))
/** Calculate the number of elements in an array. */
#define ARRAY_LEN(a) ( sizeof (a) / sizeof (a)[0] )
@ -184,7 +187,7 @@ struct version {
};
/** Compare two versions. */
int version_compare(struct version *a, struct version *b);
int version_cmp(struct version *a, struct version *b);
/** Parse a dotted version string. */
int version_parse(const char *s, struct version *v);

View file

@ -101,7 +101,7 @@ int kernel_has_version(int maj, int min)
if (version_parse(uts.release, &current))
return -1;
return version_compare(&current, &required) < 0;
return version_cmp(&current, &required) < 0;
}
int kernel_is_rt()

View file

@ -65,6 +65,8 @@ static void * memory_hugepage_alloc(size_t len)
static int memory_hugepage_free(void *ptr, size_t len)
{
len = ALIGN(len, HUGEPAGESIZE); /* ugly see: https://lkml.org/lkml/2015/3/27/171 */
return munmap(ptr, len);
}

View file

@ -113,6 +113,8 @@ int node_start(struct node *n)
if (ret == 0)
n->state = NODE_RUNNING;
n->sequence = 0;
return ret;
}

View file

@ -103,8 +103,9 @@ char * socket_print(struct node *n)
}
switch (s->header) {
case SOCKET_HEADER_GTNET_SKT: header = "gtnet-skt"; break;
case SOCKET_HEADER_DEFAULT: header = "villas"; break;
case SOCKET_HEADER_NONE: header = "none"; break;
case SOCKET_HEADER_FAKE: header = "fake"; break;
case SOCKET_HEADER_DEFAULT: header = "default"; break;
}
char *local = socket_print_addr((struct sockaddr *) &s->local);
@ -214,34 +215,29 @@ int socket_read(struct node *n, struct sample *smps[], unsigned cnt)
int samples, ret, received, length;
ssize_t bytes;
if (s->header == SOCKET_HEADER_GTNET_SKT) {
if (s->header == SOCKET_HEADER_NONE || s->header == SOCKET_HEADER_FAKE) {
if (cnt < 1)
return 0;
/* The GTNETv2-SKT protocol send every sample in a single packet.
* socket_read() receives a single packet. */
int iov_len = s->header == SOCKET_HEADER_FAKE ? 2 : 1;
struct iovec iov[iov_len];
struct sample *smp = smps[0];
#if defined(GTNET_SKT_HEADER) && GTNET_SKT_HEADER
uint32_t header[3];
struct iovec iov[] = {
{ /* First three values are sequence, seconds and nano-seconds */
.iov_base = header,
.iov_len = sizeof(header)
},
#else
struct iovec iov[] = {
#endif
{ /* Remaining values are payload */
.iov_base = &smp->data,
.iov_len = SAMPLE_DATA_LEN(smp->capacity)
}
};
if (s->header == SOCKET_HEADER_FAKE) {
iov[0].iov_base = header;
iov[0].iov_len = sizeof(header);
}
/* Remaining values are payload */
iov[iov_len-1].iov_base = &smp->data;
iov[iov_len-1].iov_len = SAMPLE_DATA_LEN(smp->capacity);
struct msghdr mhdr = {
.msg_iov = iov,
.msg_iovlen = ARRAY_LEN(iov),
.msg_iovlen = iov_len,
.msg_name = (struct sockaddr *) &s->remote,
.msg_namelen = sizeof(s->remote)
};
@ -258,28 +254,23 @@ int socket_read(struct node *n, struct sample *smps[], unsigned cnt)
return -1;
}
#if defined(GTNET_SKT_HEADER) && GTNET_SKT_HEADER
length = (bytes - sizeof(header)) / SAMPLE_DATA_LEN(1);
#else
length = bytes / SAMPLE_DATA_LEN(1);
#endif
length = (s->header == SOCKET_HEADER_FAKE ? bytes - sizeof(header) : bytes) / SAMPLE_DATA_LEN(1);
if (length > smp->capacity) {
warn("Node %s received more values than supported. Dropping %u values", node_name(n), length - smp->capacity);
length = smp->capacity;
}
/** @todo Should we generate sequence no here manually?
* Or maybe optinally use the first data value as a sequence?
* However this would require the RTDS model to be changed. */
#if defined(GTNET_SKT_HEADER) && GTNET_SKT_HEADER
smp->sequence = header[0];
smp->ts.origin.tv_sec = header[1];
smp->ts.origin.tv_nsec = header[2];
#else
smp->sequence = -1;
smp->ts.origin.tv_sec = -1;
smp->ts.origin.tv_nsec = -1;
#endif
if (s->header == SOCKET_HEADER_FAKE) {
smp->sequence = header[0];
smp->ts.origin.tv_sec = header[1];
smp->ts.origin.tv_nsec = header[2];
}
else {
smp->sequence = n->sequence++; /* Fake sequence no generated by VILLASnode */
smp->ts.origin.tv_sec = -1;
smp->ts.origin.tv_nsec = -1;
}
smp->ts.received.tv_sec = -1;
smp->ts.received.tv_nsec = -1;
@ -376,35 +367,32 @@ int socket_write(struct node *n, struct sample *smps[], unsigned cnt)
int sent = 0;
/* Construct iovecs */
if (s->header == SOCKET_HEADER_GTNET_SKT) {
if (s->header == SOCKET_HEADER_NONE || s->header == SOCKET_HEADER_FAKE) {
if (cnt < 1)
return 0;
for (int i = 0; i < cnt; i++) {
#if defined(GTNET_SKT_HEADER) && GTNET_SKT_HEADER
uint32_t header[] = {
smps[i]->sequence,
smps[i]->ts.origin.tv_sec,
smps[i]->ts.origin.tv_nsec
};
for (int i = 0; i < cnt; i++) {
int iov_len = s->header == SOCKET_HEADER_FAKE ? 2 : 1;
struct iovec iov[iov_len];
struct iovec iov[] = {
{ /* First three values are sequence, seconds and nano-seconds */
.iov_base = header,
.iov_len = sizeof(header)
},
#else
struct iovec iov[] = {
#endif
{ /* Remaining values are payload */
.iov_base = &smps[i]->data,
.iov_len = SAMPLE_DATA_LEN(smps[i]->length)
}
};
/* First three values are sequence, seconds and nano-seconds timestamps */
uint32_t header[3];
if (s->header == SOCKET_HEADER_FAKE) {
header[0] = smps[i]->sequence;
header[1] = smps[i]->ts.origin.tv_sec;
header[2] = smps[i]->ts.origin.tv_nsec;
iov[0].iov_base = header;
iov[0].iov_len = sizeof(header);
}
/* Remaining values are payload */
iov[iov_len-1].iov_base = &smps[i]->data;
iov[iov_len-1].iov_len = SAMPLE_DATA_LEN(smps[i]->length);
struct msghdr mhdr = {
.msg_iov = iov,
.msg_iovlen = ARRAY_LEN(iov),
.msg_iovlen = iov_len,
.msg_name = (struct sockaddr *) &s->remote,
.msg_namelen = sizeof(s->remote)
};
@ -480,9 +468,11 @@ int socket_parse(struct node *n, config_setting_t *cfg)
if (!config_setting_lookup_string(cfg, "header", &hdr))
s->header = SOCKET_HEADER_DEFAULT;
else {
if (!strcmp(hdr, "gtnet-skt") || (!strcmp(hdr, "none")))
s->header = SOCKET_HEADER_GTNET_SKT;
else if (!strcmp(hdr, "default") || !strcmp(hdr, "villas"))
if (!strcmp(hdr, "gtnet-skt") || (!strcmp(hdr, "none")))
s->header = SOCKET_HEADER_NONE;
else if (!strcmp(hdr, "gtnet-skt:fake") || (!strcmp(hdr, "fake")))
s->header = SOCKET_HEADER_FAKE;
else if (!strcmp(hdr, "villas") || !strcmp(hdr, "default"))
s->header = SOCKET_HEADER_DEFAULT;
else
cerror(cfg, "Invalid application header type '%s' for node %s", hdr, node_name(n));

View file

@ -35,7 +35,7 @@ int version_parse(const char *s, struct version *v)
return sscanf(s, "%u.%u", &v->major, &v->minor) != 2;
}
int version_compare(struct version *a, struct version *b) {
int version_cmp(struct version *a, struct version *b) {
int major = a->major - b->major;
int minor = a->minor - b->minor;

View file

@ -7,6 +7,10 @@ TEST_LDLIBS = $(LDLIBS) -lcriterion -lvillas -pthread
tests: $(BUILDDIR)/testsuite
run-tests: tests
echo 25 > /proc/sys/vm/nr_hugepages
$(BUILDDIR)/testsuite
# Compile
$(BUILDDIR)/tests/%.o: tests/%.c | $$(dir $$@)
$(CC) $(TEST_CFLAGS) -c $< -o $@
@ -25,4 +29,4 @@ install-tests:
clean-tests:
rm -rf $(BUILDDIR)/tests $(BUILDDIR)/testsuite
.PHONY: tests install-tests clean-tests
.PHONY: tests install-tests clean-tests run-tests

39
tests/memory.c Normal file
View file

@ -0,0 +1,39 @@
/** Unit tests for memory management
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2014-2016, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
*********************************************************************************/
#include <criterion/criterion.h>
#include <criterion/theories.h>
#include <errno.h>
#include "memory.h"
#include "utils.h"
TheoryDataPoints(memory, aligned) = {
// DataPoints(size_t, 1, 32, 55, 1 << 10, 1 << 20),
DataPoints(size_t, 1<<12),
DataPoints(size_t, 1, 8, 1 << 12),
DataPoints(const struct memtype *, &memtype_heap, &memtype_hugepage)
};
Theory((size_t len, size_t align, const struct memtype *m), memory, aligned) {
int ret;
void *ptr;
ptr = memory_alloc_aligned(m, len, align);
cr_assert_neq(ptr, NULL, "Failed to allocate memory");
//cr_assert(IS_ALIGNED(ptr, align));
if (m == &memtype_hugepage) {
cr_assert(IS_ALIGNED(ptr, HUGEPAGESIZE));
}
ret = memory_free(m, ptr, len);
cr_assert_eq(ret, 0, "Failed to release memory: ret=%d, ptr=%p, len=%zu: %s", ret, ptr, len, strerror(errno));
}

68
tests/pool.c Normal file
View file

@ -0,0 +1,68 @@
/** Unit tests for memory pool
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2014-2016, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
*********************************************************************************/
#include <criterion/criterion.h>
#include <criterion/parameterized.h>
#include <signal.h>
#include "pool.h"
#include "utils.h"
struct param {
int thread_count;
int pool_size;
size_t block_size;
const struct memtype *memtype;
};
ParameterizedTestParameters(pool, basic)
{
static struct param params[] = {
{ 1, 4096, 150, &memtype_heap },
{ 1, 128, 8, &memtype_hugepage },
{ 1, 4, 8192, &memtype_hugepage },
{ 1, 1 << 13, 4, &memtype_heap }
};
return cr_make_param_array(struct param, params, ARRAY_LEN(params));
}
ParameterizedTest(struct param *p, pool, basic)
{
int ret;
struct pool pool;
void *ptr, *ptrs[p->pool_size];
ret = pool_init(&pool, p->pool_size, p->block_size, p->memtype);
cr_assert_eq(ret, 0, "Failed to create pool");
ptr = pool_get(&pool);
cr_assert_neq(ptr, NULL);
memset(ptr, 1, p->block_size); /* check that we dont get a seg fault */
int i;
for (i = 1; i < p->pool_size; i++) {
ptrs[i] = pool_get(&pool);
if (ptrs[i] == NULL)
break;
}
if (i < p->pool_size)
cr_assert_neq(ptrs[i], NULL);
ptr = pool_get(&pool);
cr_assert_eq(ptr, NULL);
ret = pool_destroy(&pool);
cr_assert_eq(ret, 0, "Failed to destroy pool");
}

View file

@ -7,6 +7,7 @@
*********************************************************************************/
#include <unistd.h>
#include <math.h>
#include <criterion/criterion.h>
#include "timing.h"
@ -86,7 +87,10 @@ Test(timing, timerfd_create_rate, .timeout = 20)
cr_assert(tfd > 0);
for (int i = 0; i < 10; i++) {
timerfd_wait(tfd);
int i;
for (i = 0; i < 10; i++) {
start = time_now();
timerfd_wait(tfd);
@ -94,9 +98,13 @@ Test(timing, timerfd_create_rate, .timeout = 20)
end = time_now();
waited = time_delta(&start, &end);
cr_assert_float_eq(waited, 1.0 / rate, 10e-3, "We slept for %f instead of %f secs in round %d", waited, 1.0 / rate, i);
if (fabs(waited - 1.0 / rate) > 10e-3)
break;
}
if (i < 10)
cr_assert_float_eq(waited, 1.0 / rate, 10e-3, "We slept for %f instead of %f secs in round %d", waited, 1.0 / rate, i);
close(tfd);
}

64
tests/utils.c Normal file
View file

@ -0,0 +1,64 @@
/** Unit tests for utilities
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2014-2016, Institute for Automation of Complex Power Systems, EONERC
* This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
*********************************************************************************/
#include <criterion/criterion.h>
#include "utils.h"
Test(utils, is_aligned)
{
/* Positive */
cr_assert(IS_ALIGNED(1, 1));
cr_assert(IS_ALIGNED(128, 64));
/* Negative */
cr_assert(!IS_ALIGNED(55, 16));
cr_assert(!IS_ALIGNED(55, 55));
cr_assert(!IS_ALIGNED(1128, 256));
}
Test(utils, ceil)
{
cr_assert_eq(CEIL(10, 3), 4);
cr_assert_eq(CEIL(10, 5), 2);
cr_assert_eq(CEIL(4, 3), 2);
}
Test(utils, is_pow2)
{
/* Positive */
cr_assert(IS_POW2(1));
cr_assert(IS_POW2(2));
cr_assert(IS_POW2(64));
/* Negative */
cr_assert(!IS_POW2(0));
cr_assert(!IS_POW2(3));
cr_assert(!IS_POW2(11111));
cr_assert(!IS_POW2(-1));
}
struct version_param {
const char *v1, *v2;
int result;
};
Test(utils, version)
{
struct version v1, v2, v3, v4;
version_parse("1.2", &v1);
version_parse("1.3", &v2);
version_parse("55", &v3);
version_parse("66", &v4);
cr_assert_lt(version_cmp(&v1, &v2), 0);
cr_assert_eq(version_cmp(&v1, &v1), 0);
cr_assert_gt(version_cmp(&v2, &v1), 0);
cr_assert_lt(version_cmp(&v3, &v4), 0);
}

@ -1 +1 @@
Subproject commit 1b687a9f6a0e51c9e9b0a047e1fcd6c94de7a080
Subproject commit 101d0e0f3a71c414c7d2a7a2a2fa465969b6b667