From 5aa4fec093b8bf903a818ceacfaae1afe44179b1 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 12 Oct 2016 16:57:14 +0200 Subject: [PATCH 01/27] Remove sequence num ordering in case of gtnet-skt without GTNET_SKT_HEADER enabled --- config.h | 2 +- etc/gtnet_test4.conf | 4 ++-- include/villas/hooks.h | 1 + lib/hooks/hooks-internal.c | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/config.h b/config.h index 2ca343bbe..9fddb52de 100644 --- a/config.h +++ b/config.h @@ -25,7 +25,7 @@ #define DEFAULT_QUEUELEN 1024 /** Whether or not to send / receive timestamp & sequence number as first values of payload */ -#define GTNET_SKT_HEADER 1 +#define GTNET_SKT_HEADER 0 /** Width of log output in characters */ #define LOG_WIDTH 132 diff --git a/etc/gtnet_test4.conf b/etc/gtnet_test4.conf index b80cbd41d..4a56ee34d 100644 --- a/etc/gtnet_test4.conf +++ b/etc/gtnet_test4.conf @@ -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 = { diff --git a/include/villas/hooks.h b/include/villas/hooks.h index 67356df87..ad3fe07c0 100644 --- a/include/villas/hooks.h +++ b/include/villas/hooks.h @@ -22,6 +22,7 @@ #define _HOOKS_H_ #include +#include #include "queue.h" #include "list.h" diff --git a/lib/hooks/hooks-internal.c b/lib/hooks/hooks-internal.c index 26958703c..f260e0a73 100644 --- a/lib/hooks/hooks-internal.c +++ b/lib/hooks/hooks-internal.c @@ -67,6 +67,10 @@ int hook_drop(struct path *p, struct hook *h, int when, struct sample *smps[], s { int i, ok, dist; + /** Don't check the sequence number order in case of gtnet-skt without header */ + if(!strcmp(p->in->_vt->name, "socket") && !GTNET_SKT_HEADER) + return cnt; + for (i = 0, ok = 0; i < cnt; i++) { h->last = smps[i]; From a5d50ea75647735f57c11334304eb4e26f31c4af Mon Sep 17 00:00:00 2001 From: Umar Farooq Date: Mon, 17 Oct 2016 16:28:43 +0200 Subject: [PATCH 02/27] Add manual sequence numbers for GTNET w/o header as temp solution --- include/villas/node.h | 2 ++ lib/hooks/hooks-internal.c | 4 ---- lib/node.c | 2 ++ lib/nodes/socket.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/villas/node.h b/include/villas/node.h index 7cb8394db..7c09de010 100644 --- a/include/villas/node.h +++ b/include/villas/node.h @@ -50,6 +50,8 @@ struct node qptr_t sent; /**< Number of samples sent / written to this node. */ qptr_t received; /**< Number of samples received / read from this node. */ + + qptr_t seq_num; /**< Manual sequence number in case a node doesn't support it. */ enum node_state { NODE_INVALID, /**< This node object is not in a valid state. */ diff --git a/lib/hooks/hooks-internal.c b/lib/hooks/hooks-internal.c index f260e0a73..26958703c 100644 --- a/lib/hooks/hooks-internal.c +++ b/lib/hooks/hooks-internal.c @@ -67,10 +67,6 @@ int hook_drop(struct path *p, struct hook *h, int when, struct sample *smps[], s { int i, ok, dist; - /** Don't check the sequence number order in case of gtnet-skt without header */ - if(!strcmp(p->in->_vt->name, "socket") && !GTNET_SKT_HEADER) - return cnt; - for (i = 0, ok = 0; i < cnt; i++) { h->last = smps[i]; diff --git a/lib/node.c b/lib/node.c index 7baa1a407..dca14ae83 100644 --- a/lib/node.c +++ b/lib/node.c @@ -113,6 +113,8 @@ int node_start(struct node *n) if (ret == 0) n->state = NODE_RUNNING; + n->seq_num = 0; /** @todo is it the appropriate place to initialize seq_num? */ + return ret; } diff --git a/lib/nodes/socket.c b/lib/nodes/socket.c index f6c3e3f12..ecffea3c7 100644 --- a/lib/nodes/socket.c +++ b/lib/nodes/socket.c @@ -276,7 +276,7 @@ int socket_read(struct node *n, struct sample *smps[], unsigned cnt) smp->ts.origin.tv_sec = header[1]; smp->ts.origin.tv_nsec = header[2]; #else - smp->sequence = -1; + smp->sequence = n->seq_num++; smp->ts.origin.tv_sec = -1; smp->ts.origin.tv_nsec = -1; #endif From ff482493895dddc128e75b18c988b453ef0a234a Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 19 Oct 2016 01:56:00 -0400 Subject: [PATCH 03/27] fix sequence numbers for gtnet-skt --- include/villas/node.h | 2 ++ lib/node.c | 2 +- lib/nodes/socket.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/villas/node.h b/include/villas/node.h index e01863444..c24841832 100644 --- a/include/villas/node.h +++ b/include/villas/node.h @@ -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. */ diff --git a/lib/node.c b/lib/node.c index dca14ae83..b4aa9dc02 100644 --- a/lib/node.c +++ b/lib/node.c @@ -113,7 +113,7 @@ int node_start(struct node *n) if (ret == 0) n->state = NODE_RUNNING; - n->seq_num = 0; /** @todo is it the appropriate place to initialize seq_num? */ + n->sequence = 0; return ret; } diff --git a/lib/nodes/socket.c b/lib/nodes/socket.c index ecffea3c7..827c72998 100644 --- a/lib/nodes/socket.c +++ b/lib/nodes/socket.c @@ -276,7 +276,7 @@ int socket_read(struct node *n, struct sample *smps[], unsigned cnt) smp->ts.origin.tv_sec = header[1]; smp->ts.origin.tv_nsec = header[2]; #else - smp->sequence = n->seq_num++; + smp->sequence = n->sequence++; smp->ts.origin.tv_sec = -1; smp->ts.origin.tv_nsec = -1; #endif From 641d5ea7ede3fcaa08be447ab91b9b5071b7b88b Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 19 Oct 2016 01:58:55 -0400 Subject: [PATCH 04/27] fixed commenting style in socket code --- include/villas/nodes/socket.h | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/include/villas/nodes/socket.h b/include/villas/nodes/socket.h index 64c1dd82e..031034621 100644 --- a/include/villas/nodes/socket.h +++ b/include/villas/nodes/socket.h @@ -41,29 +41,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_ */ }; From 3839262d8dbae707e9f6a8a549d7f187d6d1606d Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 16 Oct 2016 02:33:36 -0400 Subject: [PATCH 05/27] mpmc_queue => queue --- include/villas/path.h | 2 +- include/villas/pool.h | 10 +++++----- include/villas/queue.h | 18 +++++++++--------- lib/path.c | 8 ++++---- lib/pool.c | 6 +++--- lib/queue.c | 22 +++++++++++----------- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/include/villas/path.h b/include/villas/path.h index 801acf51d..fb01e5d23 100644 --- a/include/villas/path.h +++ b/include/villas/path.h @@ -42,7 +42,7 @@ struct path struct node *in; /**< Pointer to the incoming node */ - struct mpmc_queue queue; /**< A ring buffer for all received messages (unmodified) */ + struct queue queue; /**< A ring buffer for all received messages (unmodified) */ struct pool pool; /**< Memory pool for messages / samples. */ struct list destinations; /**< List of all outgoing nodes */ diff --git a/include/villas/pool.h b/include/villas/pool.h index c6ffcc466..4c0fc70f4 100644 --- a/include/villas/pool.h +++ b/include/villas/pool.h @@ -28,7 +28,7 @@ struct pool { size_t blocksz; /**< Length of a block in bytes */ size_t alignment; /**< Alignment of a block in bytes */ - struct mpmc_queue queue; /**< The queue which is used to keep track of free blocks */ + struct queue queue; /**< The queue which is used to keep track of free blocks */ }; #define INLINE static inline __attribute__((unused)) @@ -42,26 +42,26 @@ int pool_destroy(struct pool *p); /** Pop cnt values from the stack an place them in the array blocks */ INLINE ssize_t pool_get_many(struct pool *p, void *blocks[], size_t cnt) { - return mpmc_queue_pull_many(&p->queue, blocks, cnt); + return queue_pull_many(&p->queue, blocks, cnt); } /** Push cnt values which are giving by the array values to the stack. */ INLINE ssize_t pool_put_many(struct pool *p, void *blocks[], size_t cnt) { - return mpmc_queue_push_many(&p->queue, blocks, cnt); + return queue_push_many(&p->queue, blocks, cnt); } /** Get a free memory block from pool. */ INLINE void * pool_get(struct pool *p) { void *ptr; - return mpmc_queue_pull(&p->queue, &ptr) == 1 ? ptr : NULL; + return queue_pull(&p->queue, &ptr) == 1 ? ptr : NULL; } /** Release a memory block back to the pool. */ INLINE int pool_put(struct pool *p, void *buf) { - return mpmc_queue_push(&p->queue, buf); + return queue_push(&p->queue, buf); } #endif /* _POOL_H_ */ \ No newline at end of file diff --git a/include/villas/queue.h b/include/villas/queue.h index bf2a6cac0..40baf1f17 100644 --- a/include/villas/queue.h +++ b/include/villas/queue.h @@ -42,12 +42,12 @@ #define CACHELINE_SIZE 64 typedef char cacheline_pad_t[CACHELINE_SIZE]; -struct mpmc_queue { +struct queue { cacheline_pad_t _pad0; /**< Shared area: all threads read */ struct memtype const * mem; size_t buffer_mask; - struct mpmc_queue_cell { + struct queue_cell { atomic_size_t sequence; void *data; } *buffer; @@ -64,24 +64,24 @@ struct mpmc_queue { }; /** Initialize MPMC queue */ -int mpmc_queue_init(struct mpmc_queue *q, size_t size, const struct memtype *mem); +int queue_init(struct queue *q, size_t size, const struct memtype *mem); /** Desroy MPMC queue and release memory */ -int mpmc_queue_destroy(struct mpmc_queue *q); +int queue_destroy(struct queue *q); /** Return estimation of current queue usage. * * Note: This is only an estimation and not accurate as long other * threads are performing operations. */ -size_t mpmc_queue_available(struct mpmc_queue *q); +size_t queue_available(struct queue *q); -int mpmc_queue_push(struct mpmc_queue *q, void *ptr); +int queue_push(struct queue *q, void *ptr); -int mpmc_queue_pull(struct mpmc_queue *q, void **ptr); +int queue_pull(struct queue *q, void **ptr); -int mpmc_queue_push_many(struct mpmc_queue *q, void *ptr[], size_t cnt); +int queue_push_many(struct queue *q, void *ptr[], size_t cnt); -int mpmc_queue_pull_many(struct mpmc_queue *q, void *ptr[], size_t cnt); +int queue_pull_many(struct queue *q, void *ptr[], size_t cnt); #endif /* _MPMC_QUEUE_H_ */ \ No newline at end of file diff --git a/lib/path.c b/lib/path.c index b783c816f..69667cbcc 100644 --- a/lib/path.c +++ b/lib/path.c @@ -26,7 +26,7 @@ static void path_write(struct path *p, bool resend) int sent, tosend, available, released; struct sample *smps[n->vectorize]; - available = mpmc_queue_pull_many(&p->queue, (void **) smps, cnt); + available = queue_pull_many(&p->queue, (void **) smps, cnt); if (available < cnt) warn("Queue underrun for path %s: available=%u expected=%u", path_name(p), available, cnt); @@ -108,7 +108,7 @@ static void * path_run(void *arg) p->skipped += recv - enqueue; } - enqueued = mpmc_queue_push_many(&p->queue, (void **) smps, enqueue); + enqueued = queue_push_many(&p->queue, (void **) smps, enqueue); if (enqueue != enqueued) warn("Failed to enqueue %u samples for path %s", enqueue - enqueued, path_name(p)); @@ -219,7 +219,7 @@ int path_prepare(struct path *p) if (ret) error("Failed to allocate memory pool for path"); - ret = mpmc_queue_init(&p->queue, p->queuelen, &memtype_hugepage); + ret = queue_init(&p->queue, p->queuelen, &memtype_hugepage); if (ret) error("Failed to initialize queue for path"); @@ -233,7 +233,7 @@ void path_destroy(struct path *p) list_destroy(&p->destinations, NULL, false); list_destroy(&p->hooks, NULL, true); - mpmc_queue_destroy(&p->queue); + queue_destroy(&p->queue); pool_destroy(&p->pool); free(p->_name); diff --git a/lib/pool.c b/lib/pool.c index 90317bd9e..156200c5a 100644 --- a/lib/pool.c +++ b/lib/pool.c @@ -26,17 +26,17 @@ int pool_init(struct pool *p, size_t blocksz, size_t cnt, const struct memtype * else debug(DBG_POOL | 4, "Allocated %#zx bytes for memory pool", p->len); - mpmc_queue_init(&p->queue, cnt, m); + queue_init(&p->queue, cnt, m); for (int i = 0; i < cnt; i++) - mpmc_queue_push(&p->queue, (char *) p->buffer + i * p->blocksz); + queue_push(&p->queue, (char *) p->buffer + i * p->blocksz); return 0; } int pool_destroy(struct pool *p) { - mpmc_queue_destroy(&p->queue); + queue_destroy(&p->queue); return memory_free(p->mem, p->buffer, p->len); } \ No newline at end of file diff --git a/lib/queue.c b/lib/queue.c index 136a7d263..1d1b797bd 100644 --- a/lib/queue.c +++ b/lib/queue.c @@ -34,7 +34,7 @@ #include "queue.h" /** Initialize MPMC queue */ -int mpmc_queue_init(struct mpmc_queue *q, size_t size, const struct memtype *mem) +int queue_init(struct queue *q, size_t size, const struct memtype *mem) { /* Queue size must be 2 exponent */ if ((size < 2) || ((size & (size - 1)) != 0)) @@ -55,7 +55,7 @@ int mpmc_queue_init(struct mpmc_queue *q, size_t size, const struct memtype *mem return 0; } -int mpmc_queue_destroy(struct mpmc_queue *q) +int queue_destroy(struct queue *q) { return memory_free(q->mem, q->buffer, (q->buffer_mask + 1) * sizeof(sizeof(q->buffer[0]))); } @@ -65,15 +65,15 @@ int mpmc_queue_destroy(struct mpmc_queue *q) * Note: This is only an estimation and not accurate as long other * threads are performing operations. */ -size_t mpmc_queue_available(struct mpmc_queue *q) +size_t queue_available(struct queue *q) { return atomic_load_explicit(&q->tail, memory_order_relaxed) - atomic_load_explicit(&q->head, memory_order_relaxed); } -int mpmc_queue_push(struct mpmc_queue *q, void *ptr) +int queue_push(struct queue *q, void *ptr) { - struct mpmc_queue_cell *cell; + struct queue_cell *cell; size_t pos, seq; intptr_t diff; @@ -99,9 +99,9 @@ int mpmc_queue_push(struct mpmc_queue *q, void *ptr) return 1; } -int mpmc_queue_pull(struct mpmc_queue *q, void **ptr) +int queue_pull(struct queue *q, void **ptr) { - struct mpmc_queue_cell *cell; + struct queue_cell *cell; size_t pos, seq; intptr_t diff; @@ -128,13 +128,13 @@ int mpmc_queue_pull(struct mpmc_queue *q, void **ptr) return 1; } -int mpmc_queue_push_many(struct mpmc_queue *q, void *ptr[], size_t cnt) +int queue_push_many(struct queue *q, void *ptr[], size_t cnt) { int ret; size_t i; for (i = 0; i < cnt; i++) { - ret = mpmc_queue_push(q, ptr[i]); + ret = queue_push(q, ptr[i]); if (!ret) break; } @@ -142,13 +142,13 @@ int mpmc_queue_push_many(struct mpmc_queue *q, void *ptr[], size_t cnt) return i; } -int mpmc_queue_pull_many(struct mpmc_queue *q, void *ptr[], size_t cnt) +int queue_pull_many(struct queue *q, void *ptr[], size_t cnt) { int ret; size_t i; for (i = 0; i < cnt; i++) { - ret = mpmc_queue_pull(q, &ptr[i]); + ret = queue_pull(q, &ptr[i]); if (!ret) break; } From f33b1a760988dbcc7e4350e6684820c732d756e1 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 16 Oct 2016 02:33:57 -0400 Subject: [PATCH 06/27] fix deployment of web site --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d0ef8281f..449be5384 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -101,7 +101,7 @@ integration: website: stage: deploy script: - - rsync web/ $DEPLOY_PATH/ + - rsync -r web/ $DEPLOY_PATH/ only: - develop tags: From 88527482429067cf4815cd99b428677fc8440e2f Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 19 Oct 2016 01:25:05 -0400 Subject: [PATCH 07/27] rdtscp => rdtsc --- include/villas/utils.h | 4 ++-- lib/utils.c | 5 +++-- src/fpga-bench-overruns.c | 4 ++-- src/fpga-bench.c | 16 ++++++++-------- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/include/villas/utils.h b/include/villas/utils.h index 6664b2232..82b7cabb7 100644 --- a/include/villas/utils.h +++ b/include/villas/utils.h @@ -204,11 +204,11 @@ void printb(void *mem, size_t len); void printdw(void *mem, size_t len); /** Get CPU timestep counter */ -__attribute__((always_inline)) static inline uint64_t rdtscp() +__attribute__((always_inline)) static inline uint64_t rdtsc() { uint64_t tsc; - __asm__ ("rdtscp;" + __asm__ ("rdtsc;" "shl $32, %%rdx;" "or %%rdx,%%rax" : "=a" (tsc) diff --git a/lib/utils.c b/lib/utils.c index 66512c3bd..eff72a954 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -370,12 +370,13 @@ void rdtsc_sleep(uint64_t nanosecs, uint64_t start) { uint64_t cycles; + /** @todo Replace the hard coded CPU clock frequency */ cycles = (double) nanosecs / (1e9 / 3392389000); if (start == 0) - start = rdtscp(); + start = rdtsc(); do { __asm__("nop"); - } while (rdtscp() - start < cycles); + } while (rdtsc() - start < cycles); } \ No newline at end of file diff --git a/src/fpga-bench-overruns.c b/src/fpga-bench-overruns.c index f161c7696..73346b5e9 100644 --- a/src/fpga-bench-overruns.c +++ b/src/fpga-bench-overruns.c @@ -115,9 +115,9 @@ int fpga_benchmark_overruns(struct fpga *f) for (int i = 0; i < runs + BENCH_WARMUP; i++) { dma_read(dm, mem.base_phys, 0x200); - start = rdtscp(); + start = rdtsc(); lapack_workload(p, A); - stop = rdtscp(); + stop = rdtsc(); dma_read_complete(dm, NULL, NULL); diff --git a/src/fpga-bench.c b/src/fpga-bench.c index 2ab2523a0..1de87f1f3 100644 --- a/src/fpga-bench.c +++ b/src/fpga-bench.c @@ -108,7 +108,7 @@ int fpga_benchmark_jitter(struct fpga *f) XTmrCtr_SetResetValue(xtmr, 0, period * FPGA_AXI_HZ); XTmrCtr_Start(xtmr, 0); - uint64_t end, start = rdtscp(); + uint64_t end, start = rdtsc(); for (int i = 0; i < runs; i++) { uint64_t cnt = intc_wait(f->intc, tmr->irq); if (cnt != 1) @@ -117,7 +117,7 @@ int fpga_benchmark_jitter(struct fpga *f) /* Ackowledge IRQ */ XTmrCtr_WriteReg((uintptr_t) f->map + tmr->baseaddr, 0, XTC_TCSR_OFFSET, XTmrCtr_ReadReg((uintptr_t) f->map + tmr->baseaddr, 0, XTC_TCSR_OFFSET)); - end = rdtscp(); + end = rdtsc(); hist[i] = end - start; start = end; } @@ -157,11 +157,11 @@ int fpga_benchmark_latency(struct fpga *f) error("Failed to enable interrupts"); for (int i = 0; i < runs; i++) { - start = rdtscp(); + start = rdtsc(); XIntc_Out32((uintptr_t) f->map + f->intc->baseaddr + XIN_ISR_OFFSET, 0x100); intc_wait(f->intc, 8); - end = rdtscp(); + end = rdtsc(); hist[i] = end - start; } @@ -239,7 +239,7 @@ int fpga_benchmark_datamover(struct fpga *f) uint64_t runs = BENCH_RUNS >> exp; for (int i = 0; i < runs + BENCH_WARMUP; i++) { - start = rdtscp(); + start = rdtsc(); #if BENCH_DM == 1 ssize_t ret; @@ -255,7 +255,7 @@ int fpga_benchmark_datamover(struct fpga *f) if (ret) error("DMA ping pong failed"); #endif - stop = rdtscp(); + stop = rdtsc(); if (memcmp(src.base_virt, dst.base_virt, len)) warn("Compare failed"); @@ -304,13 +304,13 @@ int fpga_benchmark_memcpy(struct fpga *f) uint64_t runs = (BENCH_RUNS << 2) >> exp; for (int i = 0; i < runs + BENCH_WARMUP; i++) { - start = rdtscp(); + start = rdtsc(); for (int j = 0; j < len / 4; j++) // mapi[j] = j; // write dummy += mapi[j]; // read - end = rdtscp(); + end = rdtsc(); if (i > BENCH_WARMUP) total += end - start; From f295c7f9764c6930ae46c2ed4c9ba5d3c2b6cb32 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 19 Oct 2016 01:25:52 -0400 Subject: [PATCH 08/27] added unit test for MPMC queue --- tests/Makefile.inc | 2 +- tests/queue.c | 321 +++++++++++++++++++++++++++++++++++++++++-- thirdparty/criterion | 2 +- 3 files changed, 309 insertions(+), 16 deletions(-) diff --git a/tests/Makefile.inc b/tests/Makefile.inc index b701f5772..facbef13f 100644 --- a/tests/Makefile.inc +++ b/tests/Makefile.inc @@ -3,7 +3,7 @@ TEST_OBJS = $(patsubst %.c,$(BUILDDIR)/%.o,$(TEST_SRCS)) TEST_CFLAGS = $(CFLAGS) TEST_LDFLAGS = $(LDFLAGS) -Wl,-rpath,'$$ORIGIN' -TEST_LDLIBS = $(LDLIBS) -lcriterion -lvillas +TEST_LDLIBS = $(LDLIBS) -lcriterion -lvillas -pthread tests: $(BUILDDIR)/testsuite diff --git a/tests/queue.c b/tests/queue.c index 1c02306ba..9d30a1b4f 100644 --- a/tests/queue.c +++ b/tests/queue.c @@ -1,25 +1,318 @@ -/** Unit tests for MPMC queue - * - * @author Steffen Vogel - * @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 +#include +#include +#include +#include +#include #include +#include +#include +#include "utils.h" #include "queue.h" +#include "memory.h" -Test(queue, singleThreaded) +#define SIZE (1 << 10) + +static struct queue q; + +struct param { + int volatile start; + int thread_count; + int queue_size; + int iter_count; + int batch_size; + void * (*thread_func)(void *); + struct queue queue; + const struct memtype *memtype; +}; + +/** Get thread id as integer + * In contrast to pthread_t which is an opaque type */ +#ifdef __linux__ + #include +#endif + +uint64_t thread_get_id() { -/* struct queue q; +#ifdef __MACH__ + uint64_t id; + pthread_threadid_np(pthread_self(), &id); + return id; +#elif defined(SYS_gettid) + return (int) syscall(SYS_gettid); +#endif + return -1; +} + +/** Sleep, do nothing */ +__attribute__((always_inline)) static inline void nop() +{ + __asm__("rep nop;"); +} + +static void * producer(void *ctx) +{ + int ret; + struct param *p = (struct param *) ctx; - queue_init(&q, 100, &memtype_heap); + srand((unsigned) time(0) + thread_get_id()); + size_t nops = rand() % 1000; - srand(1337); + /** @todo Criterion cr_log() is broken for multi-threaded programs */ + //cr_log_info("producer: tid = %lu", thread_get_id()); + + /* Wait for global start signal */ + while (p->start == 0) + pthread_yield(); - for (int i = 0; i < 100; i++) - queue_push(&q, &) + //cr_log_info("producer: wait for %zd nops", nops); - queue_destroy(&q);*/ + /* Wait for a random time */ + for (size_t i = 0; i != nops; i += 1) + nop(); + + //cr_log_info("producer: start pushing"); + + /* Enqueue */ + for (unsigned long count = 0; count < p->iter_count; count++) { + do { + ret = queue_push(&p->queue, (void *) count); + pthread_yield(); + } while (ret != 1); + } + + //cr_log_info("producer: finished"); + + return NULL; +} + +static void * consumer(void *ctx) +{ + int ret; + struct param *p = (struct param *) ctx; + + srand((unsigned) time(0) + thread_get_id()); + size_t nops = rand() % 1000; + + //cr_log_info("consumer: tid = %lu", thread_get_id()); + + /* Wait for global start signal */ + while (p->start == 0) + pthread_yield(); + + //cr_log_info("consumer: wait for %zd nops", nops); + + /* Wait for a random time */ + for (size_t i = 0; i != nops; i += 1) + nop(); + + //cr_log_info("consumer: start pulling"); + + /* Dequeue */ + for (unsigned long count = 0; count < p->iter_count; count++) { + void *ptr; + + do { + ret = queue_pull(&p->queue, &ptr); + } while (ret != 1); + + //cr_log_info("consumer: %lu\n", count); + + //cr_assert_eq((intptr_t) ptr, count); + } + + //cr_log_info("consumer: finished"); + + return NULL; +} + +void * producer_consumer(void *ctx) +{ + struct param *p = (struct param *) ctx; + + srand((unsigned) time(0) + thread_get_id()); + size_t nops = rand() % 1000; + + /* Wait for global start signal */ + while (p->start == 0) + pthread_yield(); + + /* Wait for a random time */ + for (size_t i = 0; i != nops; i += 1) + nop(); + + for (int iter = 0; iter < p->iter_count; ++iter) { + for (size_t i = 0; i < p->batch_size; i++) { + void *ptr = (void *) (iter * p->batch_size + i); + while (!queue_push(&p->queue, ptr)) + pthread_yield(); /* queue full, let other threads proceed */ + } + + for (size_t i = 0; i < p->batch_size; i++) { + void *ptr; + while (!queue_pull(&p->queue, &ptr)) + pthread_yield(); /* queue empty, let other threads proceed */ + } + } + + return 0; +} + +void * producer_consumer_many(void *ctx) +{ + struct param *p = (struct param *) ctx; + + srand((unsigned) time(0) + thread_get_id()); + size_t nops = rand() % 1000; + + /* Wait for global start signal */ + while (p->start == 0) + pthread_yield(); + + /* Wait for a random time */ + for (size_t i = 0; i != nops; i += 1) + nop(); + + void *ptrs[p->batch_size]; + + for (int iter = 0; iter < p->iter_count; ++iter) { + for (size_t i = 0; i < p->batch_size; i++) + ptrs[i] = (void *) (iter * p->batch_size + i); + + int pushed = 0; + do { + pushed += queue_push_many(&p->queue, &ptrs[pushed], p->batch_size - pushed); + if (pushed != p->batch_size) + pthread_yield(); /* queue full, let other threads proceed */ + } while (pushed < p->batch_size); + + int pulled = 0; + do { + pulled += queue_pull_many(&p->queue, &ptrs[pulled], p->batch_size - pulled); + if (pulled != p->batch_size) + pthread_yield(); /* queue empty, let other threads proceed */ + } while (pulled < p->batch_size); + } + + return 0; +} + +Test(queue, single_threaded) +{ + int ret; + struct param p = { + .iter_count = 1 << 8, + .queue_size = 1 << 10, + .start = 1 /* we start immeadiatly */ + }; + + ret = queue_init(&p.queue, p.queue_size, &memtype_heap); + cr_assert_eq(ret, 0, "Failed to create queue"); + + producer(&p); + consumer(&p); + + cr_assert_eq(queue_available(&q), 0); + + ret = queue_destroy(&p.queue); + cr_assert_eq(ret, 0, "Failed to create queue"); +} + +ParameterizedTestParameters(queue, multi_threaded) +{ + static struct param params[] = { + { + .iter_count = 1 << 12, + .queue_size = 1 << 9, + .thread_count = 32, + .thread_func = producer_consumer_many, + .batch_size = 10, + .memtype = &memtype_heap + }, { + .iter_count = 1 << 8, + .queue_size = 1 << 9, + .thread_count = 4, + .thread_func = producer_consumer_many, + .batch_size = 100, + .memtype = &memtype_heap + }, { + .iter_count = 1 << 16, + .queue_size = 1 << 9, + .thread_count = 16, + .thread_func = producer_consumer_many, + .batch_size = 100, + .memtype = &memtype_heap + }, { + .iter_count = 1 << 8, + .queue_size = 1 << 9, + .thread_count = 4, + .thread_func = producer_consumer_many, + .batch_size = 10, + .memtype = &memtype_heap + }, { + .iter_count = 1 << 16, + .queue_size = 1 << 9, + .thread_count = 16, + .thread_func = producer_consumer, + .batch_size = 10, + .memtype = &memtype_hugepage + } + }; + + return cr_make_param_array(struct param, params, ARRAY_LEN(params)); +} + +ParameterizedTest(struct param *p, queue, multi_threaded, .timeout = 10) +{ + int ret, cycpop; + + pthread_t threads[p->thread_count]; + + p->start = 0; + + ret = queue_init(&p->queue, p->queue_size, &memtype_heap); + cr_assert_eq(ret, 0, "Failed to create queue"); + + uint64_t start_tsc_time, end_tsc_time; + + for (int i = 0; i < p->thread_count; ++i) + pthread_create(&threads[i], NULL, p->thread_func, &p); + + sleep(1); + + start_tsc_time = rdtsc(); + p->start = 1; + + for (int i = 0; i < p->thread_count; ++i) + pthread_join(threads[i], NULL); + + end_tsc_time = rdtsc(); + cycpop = (end_tsc_time - start_tsc_time) / p->iter_count; + + if (cycpop < 400) + cr_log_info("cycles/op: %u\n", cycpop); + else + cr_log_warn("cycles/op are very high (%u). Are you running on a a hypervisor?\n", cycpop); + + cr_assert_eq(queue_available(&q), 0); + + ret = queue_destroy(&p->queue); + cr_assert_eq(ret, 0, "Failed to create queue"); +} + +Test(queue, init_destroy) +{ + int ret; + struct queue q; + + ret = queue_init(&q, 100, &memtype_heap); + cr_assert_eq(ret, -1); /* Should fail as size is not 2^x */ + + ret = queue_init(&q, 1024, &memtype_heap); + cr_assert_eq(ret, 0); /* Should succeed */ + + ret = queue_destroy(&q); + cr_assert_eq(ret, 0); /* Should succeed */ } \ No newline at end of file diff --git a/thirdparty/criterion b/thirdparty/criterion index 5b0f2b129..1b687a9f6 160000 --- a/thirdparty/criterion +++ b/thirdparty/criterion @@ -1 +1 @@ -Subproject commit 5b0f2b129046955c004ff56ab2caada5b55ba8e3 +Subproject commit 1b687a9f6a0e51c9e9b0a047e1fcd6c94de7a080 From 9dc13bce5254e662797571fc3efb039de69085c0 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 19 Oct 2016 01:33:18 -0400 Subject: [PATCH 09/27] fix continuous integration --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 408766244..54ec74dc8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -82,7 +82,7 @@ COPY thirdparty/libwebsockets /tmp/libwebsockets RUN mkdir -p /tmp/libwebsockets/build && cd /tmp/libwebsockets/build && cmake .. && make install # Cleanup intermediate files from builds -RUN rm -rf /tmp +RUN rm -rf /tmp/* WORKDIR /villas From 5de7e7c77df246cb0e140d542e83dd2535c86c56 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 19 Oct 2016 01:34:27 -0400 Subject: [PATCH 10/27] add debug statements to memory functions --- include/villas/log.h | 1 + lib/memory.c | 15 ++++++--------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/include/villas/log.h b/include/villas/log.h index b94ccb891..c57d686bf 100644 --- a/include/villas/log.h +++ b/include/villas/log.h @@ -38,6 +38,7 @@ enum debug_facilities { DBG_CONFIG = (1 << 10), DBG_HOOK = (1 << 11), DBG_PATH = (1 << 12), + DBG_MEM = (1 << 13), /* Node-types */ DBG_SOCKET = (1 << 16), diff --git a/lib/memory.c b/lib/memory.c index e6501bf01..df4eccd7e 100644 --- a/lib/memory.c +++ b/lib/memory.c @@ -19,23 +19,20 @@ void * memory_alloc(const struct memtype *m, size_t len) { + debug(DBG_MEM | 2, "Allocating %zu byte of %s memory", len, m->name); return m->alloc(len); } void * memory_alloc_aligned(const struct memtype *m, size_t len, size_t alignment) { - warn("memory_alloc_aligned: not implemented yet!"); - return memory_alloc(m, len); -} - -void * memory_aligned_alloc(const struct memtype *m, size_t len, size_t align) -{ - warn("memory_aligned_alloc: not implemented yet. Falling back to unaligned version."); + debug(DBG_MEM | 2, "Allocating %zu byte of %zu-byte-aligned %s memory", len, alignment, m->name); + warn("%s: not implemented yet!", __FUNCTION__); return memory_alloc(m, len); } int memory_free(const struct memtype *m, void *ptr, size_t len) { + debug(DBG_MEM | 2, "Releasing %zu bytes of %s memory", len, m->name); return m->free(ptr, len); } @@ -85,7 +82,7 @@ const struct memtype memtype_hugepage = { .flags = MEMORY_MMAP | MEMORY_HUGEPAGE, .alloc = memory_hugepage_alloc, .free = memory_hugepage_free, - .alignment = 1 << 21 /* 2 MiB hugepage */ + .alignment = 21 /* 2 MiB hugepage */ }; /** @todo */ @@ -93,5 +90,5 @@ const struct memtype memtype_dma = { .name = "dma", .flags = MEMORY_DMA | MEMORY_MMAP, .alloc = NULL, .free = NULL, - .alignment = 1 << 12 + .alignment = 12 }; From 291033fd25c157a59df2cd307938f0c1f435cd70 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 19 Oct 2016 01:35:08 -0400 Subject: [PATCH 11/27] timout for timing unit tests --- tests/timing.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/timing.c b/tests/timing.c index b4ef56813..82fb8bf6f 100644 --- a/tests/timing.c +++ b/tests/timing.c @@ -76,7 +76,7 @@ Test(timing, time_to_from_double) cr_assert_float_eq(dbl, ref, 1e-9); } -Test(timing, timerfd_create_rate) +Test(timing, timerfd_create_rate, .timeout = 20) { struct timespec start, end; @@ -100,7 +100,7 @@ Test(timing, timerfd_create_rate) close(tfd); } -Test(timing, timerfd_wait_until) +Test(timing, timerfd_wait_until, .timeout = 1) { int tfd = timerfd_create(CLOCK_REALTIME, 0); From e95c50a827bca46acea980e4c8d07fed6f76b188 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 19 Oct 2016 01:35:41 -0400 Subject: [PATCH 12/27] some fixes for memory pool --- include/villas/pool.h | 5 ++--- lib/pool.c | 12 ++++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/include/villas/pool.h b/include/villas/pool.h index 4c0fc70f4..49f2f1c9a 100644 --- a/include/villas/pool.h +++ b/include/villas/pool.h @@ -15,8 +15,7 @@ #include #include "queue.h" - -struct memtype; +#include "memory.h" /** A thread-safe memory pool */ struct pool { @@ -34,7 +33,7 @@ struct pool { #define INLINE static inline __attribute__((unused)) /** Initiazlize a pool */ -int pool_init(struct pool *p, size_t blocksz, size_t alignment, const struct memtype *mem); +int pool_init(struct pool *p, size_t cnt, size_t blocksz, const struct memtype *mem); /** Destroy and release memory used by pool. */ int pool_destroy(struct pool *p); diff --git a/lib/pool.c b/lib/pool.c index 156200c5a..955b8b77a 100644 --- a/lib/pool.c +++ b/lib/pool.c @@ -12,8 +12,10 @@ #include "memory.h" #include "kernel/kernel.h" -int pool_init(struct pool *p, size_t blocksz, size_t cnt, const struct memtype *m) +int pool_init(struct pool *p, size_t cnt, size_t blocksz, const struct memtype *m) { + int ret; + /* Make sure that we use a block size that is aligned to the size of a cache line */ p->alignment = kernel_get_cacheline_size(); p->blocksz = blocksz * CEIL(blocksz, p->alignment); @@ -25,12 +27,14 @@ int pool_init(struct pool *p, size_t blocksz, size_t cnt, const struct memtype * serror("Failed to allocate memory for memory pool"); else debug(DBG_POOL | 4, "Allocated %#zx bytes for memory pool", p->len); - - queue_init(&p->queue, cnt, m); + + ret = queue_init(&p->queue, cnt, m); + if (ret) + return ret; for (int i = 0; i < cnt; i++) queue_push(&p->queue, (char *) p->buffer + i * p->blocksz); - + return 0; } From 3159729cffd01ea9341792040488b955ac745f97 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 20 Oct 2016 09:06:29 -0400 Subject: [PATCH 13/27] add new header type to socket node-type for making "fake" header in gtnet-skt mode configurable --- etc/example.conf | 20 ++++-- include/villas/nodes/socket.h | 5 +- lib/nodes/socket.c | 114 ++++++++++++++++------------------ 3 files changed, 69 insertions(+), 70 deletions(-) diff --git a/etc/example.conf b/etc/example.conf index 90ce35282..7f5ce6753 100644 --- a/etc/example.conf +++ b/etc/example.conf @@ -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! }, { diff --git a/include/villas/nodes/socket.h b/include/villas/nodes/socket.h index 031034621..4a0a6c337 100644 --- a/include/villas/nodes/socket.h +++ b/include/villas/nodes/socket.h @@ -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 { diff --git a/lib/nodes/socket.c b/lib/nodes/socket.c index 827c72998..d28644836 100644 --- a/lib/nodes/socket.c +++ b/lib/nodes/socket.c @@ -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 = n->sequence++; - 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)); From 59cd70fec909e328e3f2dc6f53ac96c09e195a02 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 20 Oct 2016 09:07:29 -0400 Subject: [PATCH 14/27] moved gtnet-skt test configrations to "etc/gtnet-skt" Maybe we should move them to "tests" as soon as we have shell scripts to run them automatically --- etc/{gtnet_test1.conf => gtnet-skt/test1.conf} | 0 etc/{gtnet_test2.conf => gtnet-skt/test2.conf} | 0 etc/{gtnet_test3.conf => gtnet-skt/test3.conf} | 0 etc/{gtnet_test4.conf => gtnet-skt/test4.conf} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename etc/{gtnet_test1.conf => gtnet-skt/test1.conf} (100%) rename etc/{gtnet_test2.conf => gtnet-skt/test2.conf} (100%) rename etc/{gtnet_test3.conf => gtnet-skt/test3.conf} (100%) rename etc/{gtnet_test4.conf => gtnet-skt/test4.conf} (100%) diff --git a/etc/gtnet_test1.conf b/etc/gtnet-skt/test1.conf similarity index 100% rename from etc/gtnet_test1.conf rename to etc/gtnet-skt/test1.conf diff --git a/etc/gtnet_test2.conf b/etc/gtnet-skt/test2.conf similarity index 100% rename from etc/gtnet_test2.conf rename to etc/gtnet-skt/test2.conf diff --git a/etc/gtnet_test3.conf b/etc/gtnet-skt/test3.conf similarity index 100% rename from etc/gtnet_test3.conf rename to etc/gtnet-skt/test3.conf diff --git a/etc/gtnet_test4.conf b/etc/gtnet-skt/test4.conf similarity index 100% rename from etc/gtnet_test4.conf rename to etc/gtnet-skt/test4.conf From 8044cc6fa1b6c3fd1c687f9aee338b5886be9c70 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 20 Oct 2016 09:12:42 -0400 Subject: [PATCH 15/27] remove compile-time configuration --- config.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/config.h b/config.h index 9fddb52de..087f7b3f9 100644 --- a/config.h +++ b/config.h @@ -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 0 - /** Width of log output in characters */ #define LOG_WIDTH 132 From d50f5ea694faa07c1b232d96deda4f9803ffdfbd Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 18:38:47 -0400 Subject: [PATCH 16/27] added unit tests for utils --- include/villas/utils.h | 2 +- lib/kernel/kernel.c | 2 +- lib/utils.c | 2 +- tests/utils.c | 64 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 tests/utils.c diff --git a/include/villas/utils.h b/include/villas/utils.h index 82b7cabb7..d6faadff2 100644 --- a/include/villas/utils.h +++ b/include/villas/utils.h @@ -180,7 +180,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); diff --git a/lib/kernel/kernel.c b/lib/kernel/kernel.c index 6548dbf49..b2c9b94cc 100644 --- a/lib/kernel/kernel.c +++ b/lib/kernel/kernel.c @@ -101,7 +101,7 @@ int kernel_has_version(int maj, int min) if (version_parse(uts.release, ¤t)) return -1; - return version_compare(¤t, &required) < 0; + return version_cmp(¤t, &required) < 0; } int kernel_is_rt() diff --git a/lib/utils.c b/lib/utils.c index eff72a954..5fba36038 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -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; diff --git a/tests/utils.c b/tests/utils.c new file mode 100644 index 000000000..6999cd70e --- /dev/null +++ b/tests/utils.c @@ -0,0 +1,64 @@ +/** Unit tests for utilities + * + * @author Steffen Vogel + * @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 + +#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); +} \ No newline at end of file From f13c6c14e413daf408a28e105edd521a2360db99 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 18:39:22 -0400 Subject: [PATCH 17/27] added unit tests for memory functions --- include/villas/memory.h | 1 + tests/memory.c | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/memory.c diff --git a/include/villas/memory.h b/include/villas/memory.h index d66955727..b21d1b1a4 100644 --- a/include/villas/memory.h +++ b/include/villas/memory.h @@ -8,6 +8,7 @@ */ #include +#include #ifndef _MEMORY_H_ #define _MEMORY_H_ diff --git a/tests/memory.c b/tests/memory.c new file mode 100644 index 000000000..fc40bd610 --- /dev/null +++ b/tests/memory.c @@ -0,0 +1,42 @@ +/** Unit tests for memory management + * + * @author Steffen Vogel + * @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 +#include + +#include + +#include "memory.h" +#include "utils.h" + +#define HUGEPAGESIZE (1 << 21) + +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)); + len = ALIGN(len, HUGEPAGESIZE); /* ugly see: https://lkml.org/lkml/2015/3/27/171 */ + } + + 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)); +} \ No newline at end of file From 7baffe66c0494464e237ee4acafa8c9e586b9164 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 19:40:31 -0400 Subject: [PATCH 18/27] move munmap() workaround to from unit test to memory_free_hugepage() --- include/villas/memory.h | 2 ++ lib/memory.c | 2 ++ tests/memory.c | 7 ++----- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/villas/memory.h b/include/villas/memory.h index b21d1b1a4..cf1d7acd5 100644 --- a/include/villas/memory.h +++ b/include/villas/memory.h @@ -13,6 +13,8 @@ #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); diff --git a/lib/memory.c b/lib/memory.c index df4eccd7e..7525a1c0c 100644 --- a/lib/memory.c +++ b/lib/memory.c @@ -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); } diff --git a/tests/memory.c b/tests/memory.c index fc40bd610..234e6defd 100644 --- a/tests/memory.c +++ b/tests/memory.c @@ -14,8 +14,6 @@ #include "memory.h" #include "utils.h" -#define HUGEPAGESIZE (1 << 21) - TheoryDataPoints(memory, aligned) = { // DataPoints(size_t, 1, 32, 55, 1 << 10, 1 << 20), DataPoints(size_t, 1<<12), @@ -31,12 +29,11 @@ Theory((size_t len, size_t align, const struct memtype *m), memory, aligned) { 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)); - len = ALIGN(len, HUGEPAGESIZE); /* ugly see: https://lkml.org/lkml/2015/3/27/171 */ } - + 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)); } \ No newline at end of file From 4e7011bba6f2348044bac1c6747b7f7b81b5ca21 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 19:40:50 -0400 Subject: [PATCH 19/27] added unit tests for pool --- tests/pool.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/pool.c diff --git a/tests/pool.c b/tests/pool.c new file mode 100644 index 000000000..68a8956fd --- /dev/null +++ b/tests/pool.c @@ -0,0 +1,62 @@ +/** Unit tests for memory pool + * + * @author Steffen Vogel + * @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 +#include + +#include + +#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 */ + + for (int i = 1; i < p->pool_size; i++) { + ptrs[i] = pool_get(&pool); + 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"); + +} \ No newline at end of file From 0d366fb556dac562a79092efef7953c7ebc6e041 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 19:43:22 -0400 Subject: [PATCH 20/27] speedup of unit tests (see https://github.com/Snaipe/Criterion/issues/172) --- tests/pool.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/pool.c b/tests/pool.c index 68a8956fd..8654ad6ea 100644 --- a/tests/pool.c +++ b/tests/pool.c @@ -27,7 +27,7 @@ ParameterizedTestParameters(pool, basic) { 1, 4096, 150, &memtype_heap }, { 1, 128, 8, &memtype_hugepage }, { 1, 4, 8192, &memtype_hugepage }, - { 1, 1 << 13,4, &memtype_heap } + { 1, 1 << 13, 4, &memtype_heap } }; return cr_make_param_array(struct param, params, ARRAY_LEN(params)); @@ -48,11 +48,17 @@ ParameterizedTest(struct param *p, pool, basic) memset(ptr, 1, p->block_size); /* check that we dont get a seg fault */ - for (int i = 1; i < p->pool_size; i++) { + int i; + for (i = 1; i < p->pool_size; i++) { ptrs[i] = pool_get(&pool); - cr_assert_neq(ptrs[i], NULL); + + 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); From c0645ab839dd051f4f91581615ee2887e250fddd Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 19:46:42 -0400 Subject: [PATCH 21/27] use latest Criterion version for some required bug fixes --- thirdparty/criterion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/criterion b/thirdparty/criterion index 1b687a9f6..101d0e0f3 160000 --- a/thirdparty/criterion +++ b/thirdparty/criterion @@ -1 +1 @@ -Subproject commit 1b687a9f6a0e51c9e9b0a047e1fcd6c94de7a080 +Subproject commit 101d0e0f3a71c414c7d2a7a2a2fa465969b6b667 From 1cfa7f05d9c57de0532d1a17faab3644e579e7f0 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 20:04:17 -0400 Subject: [PATCH 22/27] added missing defines --- include/villas/utils.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/villas/utils.h b/include/villas/utils.h index d6faadff2..8a50b6754 100644 --- a/include/villas/utils.h +++ b/include/villas/utils.h @@ -57,6 +57,12 @@ #define CEIL(x, y) ((x + y - 1) / y) +/** Get nearest up-rounded power of 2 */ +#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] ) From f2bdba06d071de149f4a54ca43d1a156c61aa222 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 20:07:47 -0400 Subject: [PATCH 23/27] fix submodule url for upstream bleeding branch --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index a61e863eb..79fac9332 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 From 6c23cd23d321357899ec048c3da8c312a390b451 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 20:12:48 -0400 Subject: [PATCH 24/27] add 'git submodule sync' to CI script to update changed .gitmodules --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 449be5384..8530cdc7a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 . From cbf74f931c355cbc0a59d8f4d9400ca4d3550294 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 20:58:27 -0400 Subject: [PATCH 25/27] make sure we have enough huge pages reserved for CI tests --- .gitlab-ci.yml | 3 +-- tests/Makefile.inc | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8530cdc7a..e9a1324ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -79,8 +79,7 @@ unit: dependencies: - build script: - - make tests - - build/release/testsuite + - make run-tests image: $DOCKER_REGISTRY/$DOCKER_IMAGE tags: - docker diff --git a/tests/Makefile.inc b/tests/Makefile.inc index facbef13f..9bac51525 100644 --- a/tests/Makefile.inc +++ b/tests/Makefile.inc @@ -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 \ No newline at end of file +.PHONY: tests install-tests clean-tests run-tests \ No newline at end of file From 1cede55db57671a259142f8ae89af8acf94bf359 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 21:14:48 -0400 Subject: [PATCH 26/27] make timer unit test more robust --- tests/timing.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/timing.c b/tests/timing.c index 82fb8bf6f..92974d1be 100644 --- a/tests/timing.c +++ b/tests/timing.c @@ -86,6 +86,8 @@ Test(timing, timerfd_create_rate, .timeout = 20) cr_assert(tfd > 0); + timerfd_wait(tfd); + for (int i = 0; i < 10; i++) { start = time_now(); From 2ca9193c8570eaa46b19eb9a050ec818147e1352 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 30 Oct 2016 21:21:07 -0400 Subject: [PATCH 27/27] still not robust enough (Criterion cr_assert_ calls show high latency :-S) --- tests/timing.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/timing.c b/tests/timing.c index 92974d1be..89866c07f 100644 --- a/tests/timing.c +++ b/tests/timing.c @@ -7,6 +7,7 @@ *********************************************************************************/ #include +#include #include #include "timing.h" @@ -88,7 +89,8 @@ Test(timing, timerfd_create_rate, .timeout = 20) timerfd_wait(tfd); - for (int i = 0; i < 10; i++) { + int i; + for (i = 0; i < 10; i++) { start = time_now(); timerfd_wait(tfd); @@ -96,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); }