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 'feature-mpmc-queue' into feature-curlio

This commit is contained in:
Steffen Vogel 2017-03-05 10:06:32 -04:00
commit 9c40931a04
73 changed files with 6516 additions and 1470 deletions

View file

@ -21,6 +21,10 @@
#define DEFAULT_VALUES 64
#define DEFAULT_QUEUELEN 1024
/** Number of hugepages which are requested from the the kernel.
* @see https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt */
#define DEFAULT_NR_HUGEPAGES 25
/** Width of log output in characters */
#define LOG_WIDTH 132
@ -58,4 +62,4 @@
/** AXI Bus frequency for all components
* except RTDS AXI Stream bridge which runs at RTDS_HZ (100 Mhz) */
#define FPGA_AXI_HZ 125000000 // 125 MHz
#define FPGA_AXI_HZ 125000000 // 125 MHz

View file

@ -1,9 +1,5 @@
# Global configuration file for VILLASnode
#
# This example includes all valid configuration options for the server.
# Please note, that using all options at the same time does not really
# makes sense. The purpose of this example is to serve as a reference.
#
# The syntax of this file is similar to JSON.
# A detailed description of the format can be found here:
# http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files
@ -17,23 +13,32 @@
affinity = 0x01; # Mask of cores the server should run on
# This also maps the NIC interrupts to those cores!
#priority = 50; # Priority for the server tasks.
//priority = 50; # Priority for the server tasks.
# Usually the server is using a real-time FIFO
# scheduling algorithm
# See: https://github.com/docker/docker/issues/22380
# on why we cant use real-time scheduling in Docker
debug = 5; # The level of verbosity for debug messages
# Higher number => increased verbosity
stats = 3; # The interval in seconds to print path statistics.
# A value of 0 disables the statistics.
name = "villas-acs" # The name of this VILLASnode. Might by used by node-types
# to identify themselves (default is the hostname).
log = {
level = 5; # The level of verbosity for debug messages
# Higher number => increased verbosity
faciltities = [ "path", "socket" ]; # The list of enabled debug faciltities.
# If omitted, all faciltities are enabled
# For a full list of available faciltities, check lib/log.c
file = "/var/log/villas-node.log"; # File for logs
};
http = {
htdocs = "/villas/contrib/websocket", # Root directory of internal webserver
htdocs = "/villas/web/socket/", # Root directory of internal webserver
port = 80 # Port for HTTP connections
}

View file

@ -33,7 +33,8 @@ nodes = {
series = (
{ label = "Random walk" },
{ label = "Sine" },
{ label = "Rect" }
{ label = "Rect" },
{ label = "Ramp" }
)
}
};

View file

@ -50,7 +50,6 @@ struct api_buffer {
char *buf; /**< A pointer to the buffer. Usually resized via realloc() */
size_t size; /**< The allocated size of the buffer. */
size_t len; /**< The used length of the buffer. */
size_t sent; /**< Pointer to api_buffer::buf to indicate how much has been sent. */
};
/** A connection via HTTP REST or WebSockets to issue API actions. */
@ -69,6 +68,8 @@ struct api_session {
struct api_buffer headers; /**< HTTP headers */
} response;
bool completed; /**< Did we receive the complete body yet? */
struct api *api;
};
@ -92,7 +93,7 @@ int api_destroy(struct api *a);
int api_deinit(struct api *a);
int api_session_init(struct api_session *s, enum api_mode m);
int api_session_init(struct api_session *s, struct api *a, enum api_mode m);
int api_session_destroy(struct api_session *s);

View file

@ -32,7 +32,7 @@ struct cfg {
struct api api;
struct web web;
config_t *cfg; /**< Pointer to configuration file */
config_t cfg; /**< Pointer to configuration file */
json_t *json; /**< JSON representation of the same config. */
};
@ -60,63 +60,3 @@ int cfg_destroy(struct cfg *cfg);
* @retval <0 Error. Something went wrong.
*/
int cfg_parse(struct cfg *cfg, const char *uri);
/** Parse the global section of a configuration file.
*
* @param cfg A libconfig object pointing to the root of the file
* @param set The global configuration file
* @retval 0 Success. Everything went well.
* @retval <0 Error. Something went wrong.
*/
int cfg_parse_global(config_setting_t *cfg, struct cfg *set);
/** Parse a single path and add it to the global configuration.
*
* @param cfg A libconfig object pointing to the path
* @param paths Add new paths to this linked list
* @param nodes A linked list of all existing nodes
* @param set The global configuration structure
* @retval 0 Success. Everything went well.
* @retval <0 Error. Something went wrong.
*/
int cfg_parse_path(config_setting_t *cfg,
struct list *paths, struct list *nodes, struct cfg *set);
/** Parse an array or single node and checks if they exist in the "nodes" section.
*
* Examples:
* out = [ "sintef", "scedu" ]
* out = "acs"
*
* @param cfg The libconfig object handle for "out".
* @param nodes The nodes will be added to this list.
* @param all This list contains all valid nodes.
*/
int cfg_parse_nodelist(config_setting_t *cfg, struct list *nodes, struct list *all);
/** Parse an array or single hook function.
*
* Examples:
* hooks = [ "print", "fir" ]
* hooks = "log"
**/
int cfg_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 cfg_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.
* @param nodes Add new nodes to this linked list.
* @param set The global configuration structure
* @retval 0 Success. Everything went well.
* @retval <0 Error. Something went wrong.
*/
int cfg_parse_node(config_setting_t *cfg, struct list *nodes, struct cfg *set);

View file

@ -8,13 +8,18 @@
#pragma once
#include <stdio.h>
#include <stdint.h>
#include "config.h"
#ifdef WITH_JANSSON
#include <jansson.h>
#endif
#define HIST_HEIGHT (LOG_WIDTH - 55)
#define HIST_SEQ 17
typedef unsigned hist_cnt_t;
typedef uintmax_t hist_cnt_t;
/** Histogram structure used to collect statistics. */
struct hist {
@ -39,7 +44,7 @@ struct hist {
};
/** Initialize struct hist with supplied values and allocate memory for buckets. */
void hist_create(struct hist *h, double start, double end, double resolution);
int hist_create(struct hist *h, double start, double end, double resolution);
/** Free the dynamically allocated memory. */
int hist_destroy(struct hist *h);
@ -60,7 +65,7 @@ double hist_mean(struct hist *h);
double hist_stddev(struct hist *h);
/** Print all statistical properties of distribution including a graphilcal plot of the histogram. */
void hist_print(struct hist *h);
void hist_print(struct hist *h, int details);
/** Print ASCII style plot of histogram */
void hist_plot(struct hist *h);
@ -72,4 +77,10 @@ void hist_plot(struct hist *h);
char * hist_dump(struct hist *h);
/** Prints Matlab struct containing all infos to file. */
void hist_matlab(struct hist *h, FILE *f);
int hist_dump_matlab(struct hist *h, FILE *f);
#ifdef WITH_JANSSON
int hist_dump_json(struct hist *h, FILE *f);
json_t * hist_json(struct hist *h);
#endif

View file

@ -25,39 +25,46 @@
#include "list.h"
#include "cfg.h"
#define REGISTER_HOOK(nam, desc, prio, hist, fnc, typ) \
__attribute__((constructor)) void __register_ ## fnc () { \
static struct hook h = { \
.name = nam, \
.description = desc, \
.priority = prio, \
.history = hist, \
.type = typ, \
.cb = fnc \
}; \
list_push(&hooks, &h); \
}
/* Forward declarations */
struct path;
struct hook;
struct sample;
struct cfg;
/** This is a list of hooks which can be used in the configuration file. */
extern struct list hooks;
/** Optional parameters to hook callbacks */
struct hook_info {
struct path *path;
struct list *nodes;
struct list *paths;
struct sample **smps;
size_t cnt;
};
/** 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 m A pointer to the first message which should be processed by the hook.
* @param cnt The number of messages which should be processed by 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.
* @param i The hook_info structure contains references to the current node, path or samples. Some fields of this structure can be NULL.
* @retval 0 Success. Continue processing and forwarding the message.
* @retval <0 Error. Drop the message.
*/
typedef int (*hook_cb_t)(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
typedef int (*hook_cb_t)(struct hook *h, int when, struct hook_info *i);
/** Destructor callback for hook_storage()
*
* @param data A pointer to the data which should be destroyed.
*/
typedef int (*dtor_cb_t)(void *);
/** Constructor callback for hook_storage() */
typedef int (*ctor_cb_t)(void *);
enum hook_state {
HOOK_DESTROYED,
HOOK_INITIALIZED
};
/** The type of a hook defines when a hook will be exectuted. This is used as a bitmask. */
enum hook_type {
@ -71,22 +78,17 @@ enum hook_type {
HOOK_ASYNC = 1 << 7, /**< Called asynchronously with fixed rate (see path::rate). */
HOOK_PERIODIC = 1 << 8, /**< Called periodically. Period is set by global 'stats' option in the configuration file. */
HOOK_INIT = 1 << 9, /**< Called before path is started to parse parameters. */
HOOK_DEINIT = 1 << 10, /**< Called after path has been stopped to release memory allocated by HOOK_INIT */
HOOK_INIT = 1 << 9, /**< Called before path is started to parseHOOK_DESTROYs. */
HOOK_DESTROY = 1 << 10, /**< Called after path has been stopped to release memory allocated by HOOK_INIT */
HOOK_INTERNAL = 1 << 11, /**< Internal hooks are added to every path implicitely. */
HOOK_AUTO = 1 << 11, /**< Internal hooks are added to every path implicitely. */
HOOK_PARSE = 1 << 12, /**< Called for parsing hook arguments. */
/** @{ Classes of hooks */
/** Hooks which are using private data must allocate and free them propery. */
HOOK_STORAGE = HOOK_INIT | HOOK_DEINIT,
HOOK_STORAGE = HOOK_INIT | HOOK_DESTROY,
/** 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_STORAGE | HOOK_PATH | HOOK_READ | HOOK_PERIODIC,
/** All hooks */
HOOK_ALL = HOOK_INTERNAL - 1
HOOK_PATH = HOOK_PATH_START | HOOK_PATH_STOP | HOOK_PATH_RESTART
/** @} */
};
@ -109,10 +111,14 @@ struct hook {
};
/** Save references to global nodes, paths and settings */
void hook_init(struct cfg *cfg);
int hook_init(struct hook *h, struct cfg *cfg);
/** Sort hook list according to the their priority. See hook::priority. */
int hooks_sort_priority(const void *a, const void *b);
int hook_destroy(struct hook *h);
int hook_copy(struct hook *h, struct hook *c);
/** Compare two hook functions with their priority. Used by list_sort() */
int hook_cmp_priority(const void *a, const void *b);
/** Conditionally execute the hooks
*
@ -134,18 +140,21 @@ int hook_run(struct path *p, struct sample *smps[], size_t cnt, int when);
* @param len The size of hook prvate memory allocation.
* @return A pointer to the allocated memory region or NULL after it was released.
*/
void * hook_storage(struct hook *h, int when, size_t len);
void * hook_storage(struct hook *h, int when, size_t len, ctor_cb_t ctor, dtor_cb_t dtor);
int hook_print(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
int hook_ts(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
int hook_convert(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
int hook_decimate(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
int hook_skip_first(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
/** Parse an array or single hook function.
*
* Examples:
* hooks = [ "print", "fir" ]
* hooks = "log"
*/
int hook_parse_list(struct list *list, config_setting_t *cfg);
int hook_stats_send(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
int hook_stats(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
void hook_stats_header();
int hook_fix_ts(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
int hook_restart(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
int hook_drop(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt);
/** 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 hook_parse(config_setting_t *cfg, struct list *list);

View file

@ -11,6 +11,8 @@
#include <stdint.h>
#include "config.h"
//#include <sys/capability.h>
/** Check if current process has capability \p cap.
@ -18,7 +20,13 @@
* @retval 0 If capabilty is present.
* @retval <0 If capability is not present.
*/
//int kernel_check_cap(cap_value_t cap):
//int kernel_check_cap(cap_value_t cap);
/** Get number of reserved hugepages. */
int kernel_get_nr_hugepages();
/** Set number of reserved hugepages. */
int kernel_set_nr_hugepages(int nr);
/** Get kernel cmdline parameter
*

View file

@ -30,7 +30,7 @@ struct pci_dev {
};
struct pci {
struct list devices; /**> List of available PCI devices in the system (struct pci_dev) */
struct list devices; /**< List of available PCI devices in the system (struct pci_dev) */
};
/** Initialize Linux PCI handle.

View file

@ -9,7 +9,7 @@
#pragma once
int rt_init(struct cfg *cfg);
int rt_init(int priority, int affinity);
int rt_set_affinity(int affinity);

View file

@ -67,9 +67,9 @@ struct log {
};
/** Initialize log object */
int log_init(struct log *l);
int log_init(struct log *l, int level, long faciltities);
int log_destroy(struct log *l);
int log_deinit(struct log *l);
/** Destroy log object */
int log_destroy(struct log *l);
@ -104,6 +104,8 @@ int log_set_facility_expression(struct log *l, const char *expression);
/** Parse logging configuration. */
int log_parse(struct log *l, config_setting_t *cfg);
int log_lookup_facility(const char *facility_name);
/** Logs variadic messages to stdout.
*
* @param lvl The log level

View file

@ -12,7 +12,7 @@
#define HUGEPAGESIZE (1 << 21)
typedef void *(*memzone_allocator_t)(size_t len);
typedef void *(*memzone_allocator_t)(size_t len, size_t alignment);
typedef int (*memzone_deallocator_t)(void *ptr, size_t len);
enum memtype_flags {
@ -41,6 +41,9 @@ struct memzone {
size_t len;
};
/** Initilialize memory subsystem */
int memory_init();
/** Allocate \p len bytes memory of type \p m.
*
* @retval NULL If allocation failed.

View file

@ -22,6 +22,8 @@ extern struct list node_types; /**< Vtable for virtual node sub types */
/* Forward declarations */
struct node_type;
struct settings;
typedef struct config_setting_t config_setting_t;
/** The data structure for a node.
*
@ -209,12 +211,6 @@ int node_start(struct node *n);
*/
int node_stop(struct node *n);
/** Parse node connection details.
*
* @see node_type::parse
*/
int node_parse(struct node *n, config_setting_t *cfg);
/** Return a pointer to a string which should be used to print this node.
*
* @see node::_name
@ -246,4 +242,25 @@ int node_read(struct node *n, struct sample *smps[], unsigned cnt);
int node_write(struct node *n, struct sample *smps[], unsigned cnt);
/** @} */
/** Parse an array or single node and checks if they exist in the "nodes" section.
*
* Examples:
* out = [ "sintef", "scedu" ]
* out = "acs"
*
* @param cfg The libconfig object handle for "out".
* @param nodes The nodes will be added to this list.
* @param all This list contains all valid nodes.
*/
int node_parse_list(struct list *list, config_setting_t *cfg, struct list *all);
/** Parse a single node and add it to the global configuration.
*
* @param cfg A libconfig object pointing to the node.
* @param nodes Add new nodes to this linked list.
* @retval 0 Success. Everything went well.
* @retval <0 Error. Something went wrong.
*/
int node_parse(struct node *n, config_setting_t *cfg);
/** @} */

View file

@ -16,6 +16,8 @@
#pragma once
#include <libwebsockets.h>
#include "node.h"
#include "pool.h"
#include "queue.h"
@ -25,39 +27,36 @@ struct lws;
/** Internal data per websocket node */
struct websocket {
struct list connections; /**< List of active libwebsocket connections in server mode (struct websocket_connection) */
struct list destinations; /**< List of struct lws_client_connect_info to connect to in client mode. */
struct list connections; /**< List of active libwebsocket connections in server mode (struct websocket_connection). */
struct list destinations; /**< List of websocket servers connect to in client mode (struct websocket_destination). */
struct pool pool;
struct queue queue; /**< For samples which are received from WebSockets a */
struct queue queue_tx; /**< For samples which are sent to WebSockets */
struct queue queue_rx; /**< For samples which are received from WebSockets */
qptr_t sent;
qptr_t received;
int shutdown;
int id; /**< The index of this node */
};
struct websocket_connection {
enum {
WEBSOCKET_ESTABLISHED,
WEBSOCKET_ACTIVE,
WEBSOCKET_SHUTDOWN,
WEBSOCKET_CLOSED
} state;
struct node *node;
struct path *path;
struct queue queue; /**< For samples which are sent to the WebSocket */
struct lws *wsi;
struct {
char name[64];
char ip[64];
} peer;
qptr_t sent;
qptr_t received;
char *_name;
};
int websocket_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);

View file

@ -3,12 +3,13 @@
* @file
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*/
*********************************************************************************/
/** A path connects one input node to multiple output nodes (1-to-n).
*
* @addtogroup path Path
* @{
*********************************************************************************/
*/
#pragma once
@ -20,64 +21,66 @@
#include "hist.h"
#include "node.h"
#include "msg.h"
#include "hooks.h"
#include "queue.h"
#include "pool.h"
#include "stats.h"
/** The datastructure for a path.
*
* @todo Add support for multiple outgoing nodes
*/
/* Forward declarations */
struct cfg;
struct path_source
{
struct node *node;
struct pool pool;
int samplelen;
pthread_t tid;
};
struct path_destination
{
struct node *node;
struct queue queue;
int queuelen;
pthread_t tid;
};
/** The datastructure for a path. */
struct path
{
enum {
PATH_INVALID, /**< Path is invalid. */
PATH_CREATED, /**< Path has been created. */
PATH_INITIALIZED, /**< Path queues, memory pools & hook system initialized. */
PATH_RUNNING, /**< Path is currently running. */
PATH_STOPPED /**< Path has been stopped. */
PATH_STOPPED, /**< Path has been stopped. */
PATH_DESTROYED /**< Path is destroyed. */
} state; /**< Path state */
struct node *in; /**< Pointer to the incoming node */
struct queue queue; /**< A ring buffer for all received messages (unmodified) */
struct pool pool; /**< Memory pool for messages / samples. */
/* Each path has a single source and multiple destinations */
struct path_source *source; /**< Pointer to the incoming node */
struct list destinations; /**< List of all outgoing nodes (struct path_destination). */
struct list destinations; /**< List of all outgoing nodes */
struct list hooks; /**< List of function pointers to hooks */
int samplelen; /**< Maximum number of values per sample for this path. */
int queuelen; /**< Size of sample queue for this path. */
int enabled; /**< Is this path enabled */
int tfd; /**< Timer file descriptor for fixed rate sending */
double rate; /**< Send messages with a fixed rate over this path */
int reverse; /**< This path as a matching reverse path */
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 */
pthread_t tid; /**< The thread id for this path */
char *_name; /**< Singleton: A string which is used to print this path to screen. */
/** The following fields are mostly managed by hook_ functions @{ */
struct stats *stats; /**< Statistic counters. This is a pointer to the statistic hooks private data. */
struct {
struct hist owd; /**< Histogram for one-way-delay (OWD) of received messages */
struct hist gap_msg; /**< Histogram for inter message timestamps (as sent by remote) */
struct hist gap_recv; /**< Histogram for inter message arrival time (as seen by this instance) */
struct hist gap_seq; /**< Histogram of sequence number displacement of received messages */
} hist;
/* Statistic counters */
uintmax_t invalid; /**< Counter for invalid messages */
uintmax_t skipped; /**< Counter for skipped messages due to hooks */
uintmax_t dropped; /**< Counter for dropped messages due to reordering */
uintmax_t overrun; /**< Counter of overruns for fixed-rate sending */
/** @} */
config_setting_t *cfg; /**< A pointer to the libconfig object which instantiated this path */
};
/** Create a path by allocating dynamic memory. */
void path_init(struct path *p);
/** Allocate memory for a new path */
struct path * path_create();
/** Initialize internal data structures. */
int path_init(struct path *p, struct cfg *cfg);
/** Check if path configuration is proper. */
int path_check(struct path *p);
/** Destroy path by freeing dynamically allocated memory.
*
@ -85,12 +88,6 @@ void path_init(struct path *p);
*/
int path_destroy(struct path *p);
/** Initialize pool queue and hooks.
*
* Should be called after path_init() and before path_start().
*/
int path_prepare(struct path *p);
/** Start a path.
*
* Start a new pthread for receiving/sending messages over this path.
@ -124,7 +121,20 @@ void path_print_stats(struct path *p);
*/
const char * path_name(struct path *p);
/** Reverse a path */
int path_reverse(struct path *p, struct path *r);
/** Check if node is used as source or destination of a path. */
int path_uses_node(struct path *p, struct node *n);
/** Parse a single path and add it to the global configuration.
*
* @param cfg A libconfig object pointing to the path
* @param p Pointer to the allocated memory for this path
* @param nodes A linked list of all existing nodes
* @retval 0 Success. Everything went well.
* @retval <0 Error. Something went wrong.
*/
int path_parse(struct path *p, config_setting_t *cfg, struct list *nodes);
/** @} */

View file

@ -7,7 +7,7 @@
#pragma once
#include "hooks.h"
#include "hook.h"
#include "api.h"
#include "fpga/ip.h"
@ -17,10 +17,8 @@
#define REGISTER_PLUGIN(p) \
__attribute__((constructor)) static void UNIQUE(__ctor)() { \
list_push(&plugins, p); \
(p)->load(p); \
} \
__attribute__((destructor)) static void UNIQUE(__dtor)() { \
(p)->unload(p); \
list_remove(&plugins, p); \
}

View file

@ -51,15 +51,15 @@ struct queue {
void *data;
} *buffer;
cacheline_pad_t _pad1; /**> Producer area: only producers read & write */
cacheline_pad_t _pad1; /**< Producer area: only producers read & write */
atomic_size_t tail; /**> Queue tail pointer */
atomic_size_t tail; /**< Queue tail pointer */
cacheline_pad_t _pad2; /**> Consumer area: only consumers read & write */
cacheline_pad_t _pad2; /**< Consumer area: only consumers read & write */
atomic_size_t head; /**> Queue head pointer */
atomic_size_t head; /**< Queue head pointer */
cacheline_pad_t _pad3; /**> @todo Why needed? */
cacheline_pad_t _pad3; /**< @todo Why needed? */
};
/** Initialize MPMC queue */

View file

@ -42,8 +42,7 @@ struct sample {
atomic_int refcnt; /**< Reference counter. */
struct pool *pool; /**< This sample is belong to this memory pool. */
int endian; /**< Endianess of data in the sample. */
struct node *source; /**< The node from which this sample originates. */
/** All timestamps are seconds / nano seconds after 1.1.1970 UTC */
struct {
@ -54,13 +53,18 @@ struct sample {
/** The values. */
union {
float f; /**< Floating point values (note msg::endian) */
uint32_t i; /**< Integer values (note msg::endian) */
} data[];
float f; /**< Floating point values. */
uint32_t i; /**< Integer values. */
} data[]; /**< Data is in host endianess! */
};
/** Request \p cnt samples from memory pool \p p and initialize them. */
int sample_get_many(struct pool *p, struct sample *smps[], int cnt);
/** Request \p cnt samples from memory pool \p p and initialize them.
* This will leave the reference count of the sample to zero.
* Use the sample_get() function to increase it. */
int sample_alloc(struct pool *p, struct sample *smps[], int cnt);
/** Release an array of samples back to their pools */
void sample_free(struct sample *smps[], int cnt);
/** Increase reference count of sample */
int sample_get(struct sample *s);

73
include/villas/stats.h Normal file
View file

@ -0,0 +1,73 @@
/** Statistic collection.
*
* @file
* @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.
*/
#ifndef _STATS_H_
#define _STATS_H_
#include <stdint.h>
#ifdef WITH_JANSSON
#include <jansson.h>
#endif
#include "hist.h"
/* Forward declarations */
struct sample;
struct path;
struct node;
enum stats_id {
STATS_SKIPPED, /**< Counter for skipped messages due to hooks */
STATS_DROPPED, /**< Counter for dropped messages due to reordering */
STATS_GAP_SEQUENCE, /**< Histogram of sequence number displacement of received messages */
STATS_GAP_SAMPLE, /**< Histogram for inter message timestamps (as sent by remote) */
STATS_GAP_RECEIVED, /**< Histogram for inter message arrival time (as seen by this instance) */
STATS_OWD, /**< Histogram for one-way-delay (OWD) of received messages */
STATS_COUNT /**< Just here to have an updated number of statistics */
};
struct stats_delta {
double values[STATS_COUNT];
int update; /**< Bitmask of stats_id. Only those which are masked will be updated */
struct sample *last;
};
struct stats {
struct hist histograms[STATS_COUNT];
struct stats_delta *delta;
};
int stats_init(struct stats *s);
void stats_destroy(struct stats *s);
void stats_update(struct stats_delta *s, enum stats_id id, double val);
void stats_collect(struct stats_delta *s, struct sample *smps[], size_t cnt);
int stats_commit(struct stats *s, struct stats_delta *d);
#ifdef WITH_JANSSON
json_t * stats_json(struct stats *s);
#endif
void stats_reset(struct stats *s);
void stats_print_header();
void stats_print_periodic(struct stats *s, struct path *p);
void stats_print(struct stats *s, int details);
void stats_send(struct stats *s, struct node *n);
#endif /* _STATS_H_ */

View file

@ -201,12 +201,6 @@ int version_parse(const char *s, struct version *v);
/** Fill buffer with random data */
ssize_t read_random(char *buf, size_t len);
/** Hexdump bytes */
void printb(void *mem, size_t len);
/** Hexdump 32-bit dwords */
void printdw(void *mem, size_t len);
/** Get CPU timestep counter */
__attribute__((always_inline)) static inline uint64_t rdtsc()
{
@ -224,7 +218,8 @@ __attribute__((always_inline)) static inline uint64_t rdtsc()
/** Get log2 of long long integers */
static inline int log2i(long long x) {
assert(x > 0);
if (x == 0)
return 1;
return sizeof(x) * 8 - __builtin_clzll(x) - 1;
}

View file

@ -0,0 +1,102 @@
/** Binary websocket message format
*
* @file
* @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.
*********************************************************************************/
#ifndef _WEBMSG_FORMAT_H_
#define _WEBMSG_FORMAT_H_
#include <stdint.h>
#ifdef __linux__
#define _BSD_SOURCE 1
#include <endian.h>
#elif defined(__PPC__) /* Xilinx toolchain */
#include <lwip/arch.h>
#endif
/** The current version number for the message format */
#define WEBMSG_VERSION 1
/** @todo Implement more message types */
#define WEBMSG_TYPE_DATA 0 /**< Message contains float values */
#define WEBMSG_TYPE_START 1 /**< Message marks the beginning of a new simulation case */
#define WEBMSG_TYPE_STOP 2 /**< Message marks the end of a simulation case */
#define WEBMSG_ENDIAN_LITTLE 0 /**< Message values are in little endian format (float too!) */
#define WEBMSG_ENDIAN_BIG 1 /**< Message values are in bit endian format */
#if BYTE_ORDER == LITTLE_ENDIAN
#define WEBMSG_ENDIAN_HOST MSG_ENDIAN_LITTLE
#elif BYTE_ORDER == BIG_ENDIAN
#define WEBMSG_ENDIAN_HOST MSG_ENDIAN_BIG
#else
#error "Unknown byte order!"
#endif
/** The total size in bytes of a message */
#define WEBMSG_LEN(values) (sizeof(struct msg) + MSG_DATA_LEN(values))
/** The length of \p values values in bytes. */
#define WEBMSG_DATA_LEN(values) (sizeof(float) * (values))
/** The offset to the first data value in a message. */
#define WEBMSG_DATA_OFFSET(msg) ((char *) (msg) + offsetof(struct msg, data))
/** Initialize a message with default values */
#define WEBMSG_INIT(len, seq) (struct msg) {\
.version = WEBMSG_VERSION, \
.type = WEBMSG_TYPE_DATA, \
.endian = WEBMSG_ENDIAN_HOST, \
.length = len, \
.sequence = seq \
}
/** The timestamp of a message in struct timespec format */
#define WEBMSG_TS(msg) (struct timespec) {\
.tv_sec = (msg)->ts.sec, \
.tv_nsec = (msg)->ts.nsec \
}
/** This message format is used by all clients
*
* @diafile msg_format.dia
**/
struct webmsg
{
#if BYTE_ORDER == BIG_ENDIAN
unsigned version: 4; /**< Specifies the format of the remaining message (see MGS_VERSION) */
unsigned type : 2; /**< Data or control message (see MSG_TYPE_*) */
unsigned endian : 1; /**< Specifies the byteorder of the message (see MSG_ENDIAN_*) */
unsigned rsvd1 : 1; /**< Reserved bits */
#elif BYTE_ORDER == LITTLE_ENDIAN
unsigned rsvd1 : 1; /**< Reserved bits */
unsigned endian : 1; /**< Specifies the byteorder of the message (see MSG_ENDIAN_*) */
unsigned type : 2; /**< Data or control message (see MSG_TYPE_*) */
unsigned version: 4; /**< Specifies the format of the remaining message (see MGS_VERSION) */
#endif
uint8_t id; /**< The node index from / to which this sample received / sent to.
* Corresponds to the index of the node in the http://localhost/nodes.json array. */
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. Endianess is specified in msg::endian. */
struct {
uint32_t sec; /**< Seconds since 1970-01-01 00:00:00 */
uint32_t nsec; /**< Nanoseconds of the current second. */
} ts;
/** 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) */
} data[];
} __attribute__((packed));
#endif /* _WEBMSG_FORMAT_H_ */

View file

@ -4,9 +4,9 @@ LIBS = $(BUILDDIR)/libvillas.so
# Object files for libvillas
LIB_SRCS = $(addprefix lib/nodes/, file.c cbuilder.c) \
$(addprefix lib/kernel/, kernel.c rt.c) \
$(addprefix lib/, sample.c path.c node.c hooks.c \
$(addprefix lib/, sample.c path.c node.c hook.c \
log.c utils.c cfg.c hist.c timing.c pool.c list.c \
queue.c memory.c advio.c web.c api.c plugin.c \
queue.c memory.c advio.c web.c api.c plugin.c stats.c \
)
LIB_CFLAGS = $(CFLAGS) -fPIC

135
lib/api.c
View file

@ -17,7 +17,7 @@ static int parse_request(struct api_buffer *b, json_t **req)
if (b->len <= 0)
return -1;
*req = json_loadb(b->buf, b->len, JSON_DISABLE_EOF_CHECK, &e);
if (!*req)
return -1;
@ -29,24 +29,27 @@ static int parse_request(struct api_buffer *b, json_t **req)
memmove(dst, src, b->len - e.position);
b->len -= e.position;
return 1;
}
return 0;
return 1;
}
#if JANSSON_VERSION_HEX < 0x020A00
size_t json_dumpb(const json_t *json, char *buffer, size_t size, size_t flags)
{
char *str = json_dumps(json, flags);
size_t len = strlen(str) - 1; // not \0 terminated
char *str;
size_t len;
if (!buffer || len > size)
return len;
else
memcpy(buffer, str, len);
str = json_dumps(json, flags);
if (!str)
return 0;
len = strlen(str) - 1; // not \0 terminated
if (buffer && len <= size)
memcpy(buffer, str, len);
//free(str);
return len;
}
#endif
@ -55,9 +58,10 @@ static int unparse_response(struct api_buffer *b, json_t *res)
{
size_t len;
retry: len = json_dumpb(res, b->buf + b->len, b->size - b->len, 0);
retry: len = json_dumpb(res, b->buf + b->len, b->size - b->len, 0);
if (len > b->size - b->len) {
b->buf = realloc(b->buf, b->len + len);
b->len += len;
goto retry;
}
@ -86,13 +90,17 @@ int api_session_run_command(struct api_session *s, json_t *req, json_t **resp)
"error", "command not found",
"code", -2,
"command", rstr);
debug(LOG_API, "Running API request: %s with arguments: %s", p->name, json_dumps(args, 0));
ret = p->api.cb(&p->api, args, resp, s);
if (ret)
*resp = json_pack("{ s: s, s: d }",
"error", "command failed",
"code", ret);
debug(LOG_API, "API request completed with code: %d and output: %s", ret, json_dumps(*resp, 0));
return 0;
}
@ -103,15 +111,15 @@ int api_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *use
struct api_session *s = (struct api_session *) user;
switch (reason) {
case LWS_CALLBACK_ESTABLISHED:
api_session_init(s, API_MODE_WS);
case LWS_CALLBACK_ESTABLISHED: {
struct web *w = (struct web *) lws_context_user(lws_get_context(wsi));
api_session_init(s, w->api, API_MODE_WS);
break;
}
case LWS_CALLBACK_HTTP:
if (len < 1) {
lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
return -1;
}
case LWS_CALLBACK_HTTP: {
struct web *w = (struct web *) lws_context_user(lws_get_context(wsi));
char *uri = (char *) in;
@ -122,39 +130,21 @@ int api_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *use
debug(LOG_API, "New REST API session initiated: version = %d", s->version);
api_session_init(s, API_MODE_HTTP);
api_session_init(s, w->api, API_MODE_HTTP);
/* Prepare HTTP response header */
s->response.headers.buf = alloc(512);
unsigned char *p = (unsigned char *) s->response.headers.buf;
unsigned char *e = (unsigned char *) s->response.headers.buf + 512;
ret = lws_add_http_header_status(wsi, 200, &p, e);
if (ret)
return 1;
ret = lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *) USER_AGENT, strlen(USER_AGENT), &p, e);
if (ret)
return 1;
ret = lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *) "application/json", strlen("application/json"), &p, e);
if (ret)
return 1;
//ret = lws_add_http_header_content_length(wsi, file_len, &p, e);
//if (ret)
// return 1;
ret = lws_finalize_http_header(wsi, &p, e);
if (ret)
return 1;
*p = '\0';
s->response.headers.len = p - (unsigned char *) s->response.headers.buf + 1;
s->response.headers.sent = 0;
s->response.headers.len = 1 + asprintf(&s->response.headers.buf,
"HTTP/1.1 200 OK\r\n"
"Content-type: application/json\r\n"
"User-agent: " USER_AGENT
"\r\n"
);
/* book us a LWS_CALLBACK_HTTP_WRITEABLE callback */
lws_callback_on_writable(wsi);
break;
}
case LWS_CALLBACK_CLIENT_RECEIVE:
case LWS_CALLBACK_RECEIVE:
@ -163,7 +153,7 @@ int api_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *use
newbuf = realloc(s->request.body.buf, s->request.body.len + len);
s->request.body.buf = memcpy(newbuf + s->request.body.len, in, len);
s->request.body.buf = memcpy(newbuf + s->request.body.len, in, len);
s->request.body.len += len;
json_t *req, *resp;
@ -172,41 +162,40 @@ int api_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *use
api_session_run_command(s, req, &resp);
unparse_response(&s->response.body, resp);
debug(LOG_WEB, "Sending response: %s len=%zu", s->response.body.buf, s->response.body.len);
lws_callback_on_writable(wsi);
}
break;
}
case LWS_CALLBACK_HTTP_BODY_COMPLETION:
return -1; /* close connection */
s->completed = true;
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
case LWS_CALLBACK_HTTP_WRITEABLE: {
enum lws_write_protocol prot;
struct api_buffer *protbuf;
int sent;
if (s->mode == API_MODE_HTTP && s->response.headers.len > 0) {
sent = lws_write(wsi, s->response.headers.buf, s->response.headers.len, LWS_WRITE_HTTP_HEADERS);
if (sent > 0) {
memmove(s->response.headers.buf, s->response.headers.buf + sent, sent);
s->response.headers.len -= sent;
}
}
else if (s->response.body.len > 0) {
sent = lws_write(wsi, s->response.body.buf, s->response.body.len, LWS_WRITE_HTTP);
if (sent > 0) {
memmove(s->response.body.buf, s->response.body.buf + sent, sent);
s->response.body.len -= sent;
}
}
if (s->mode == API_MODE_HTTP && s->response.headers.sent < s->response.headers.len) {
prot = LWS_WRITE_HTTP_HEADERS;
protbuf = &s->response.headers;
}
else if (s->response.body.sent < s->response.body.len) {
prot = LWS_WRITE_HTTP;
protbuf = &s->response.body;
}
else
break;
int sent;
void *buf = (void *) (protbuf->buf + protbuf->sent);
size_t len = protbuf->len - protbuf->sent;
sent = lws_write(wsi, buf, len, prot);
if (sent > 0)
protbuf->sent += sent;
else
if (s->completed && s->response.body.len == 0)
return -1;
break;
}
@ -215,7 +204,6 @@ int api_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *use
}
return 0;
}
int api_init(struct api *a, struct cfg *cfg)
@ -239,9 +227,12 @@ int api_deinit(struct api *a)
return 0;
}
int api_session_init(struct api_session *s, enum api_mode m)
int api_session_init(struct api_session *s, struct api *a, enum api_mode m)
{
s->mode = m;
s->api = a;
s->completed = false;
s->request.body =
s->response.body =

View file

@ -14,10 +14,9 @@
static int api_config(struct api_ressource *h, json_t *args, json_t **resp, struct api_session *s)
{
if (!s->api->cfg->cfg)
return -1;
config_setting_t *cfg_root = config_root_setting(&s->api->cfg->cfg);
*resp = config_to_json(config_root_setting(s->api->cfg->cfg));
*resp = config_to_json(cfg_root);
return 0;
}

19
lib/boolean/boolean.l Normal file
View file

@ -0,0 +1,19 @@
%{
#include <stdio.h>
#include <string.h>
#include "boolean.tab.h" // to get the token types that we return
%}
%option prefix="log_expression_"
%option noyywrap
%%
[ \t\n] ; // ignore whitespace
[&] ; //operators
[a-b]+ { log_expression_lval.token = strdup(log_expression_text); return TOKEN; }
[0-9]+ { log_expression_lval.constant = atoi(log_expression_text); return CONSTANT; }
%%

241
lib/boolean/boolean.output Normal file
View file

@ -0,0 +1,241 @@
Grammatik
0 $accept: input $end
1 input: %empty
2 | exp '\n'
3 exp: comp
4 | exp '&' exp
5 | exp '|' exp
6 | exp '^' exp
7 | '~' exp
8 | '(' exp ')'
9 comp: CONSTANT
10 | TOKEN
Terminale und die Regeln, in denen sie verwendet werden
$end (0) 0
'\n' (10) 2
'&' (38) 4
'(' (40) 8
')' (41) 8
'^' (94) 6
'|' (124) 5
'~' (126) 7
error (256)
TOKEN (258) 10
CONSTANT (259) 9
NEG (260)
Nicht-Terminal und die Regeln, in denen sie verwendet werden
$accept (13)
auf der linken Seite: 0
input (14)
auf der linken Seite: 1 2, auf der rechten Seite: 0
exp (15)
auf der linken Seite: 3 4 5 6 7 8, auf der rechten Seite: 2 4 5
6 7 8
comp (16)
auf der linken Seite: 9 10, auf der rechten Seite: 3
Zustand 0
0 $accept: . input $end
TOKEN schiebe und gehe zu Zustand 1 über
CONSTANT schiebe und gehe zu Zustand 2 über
'~' schiebe und gehe zu Zustand 3 über
'(' schiebe und gehe zu Zustand 4 über
$default reduziere mit Regel 1 (input)
input gehe zu Zustand 5 über
exp gehe zu Zustand 6 über
comp gehe zu Zustand 7 über
Zustand 1
10 comp: TOKEN .
$default reduziere mit Regel 10 (comp)
Zustand 2
9 comp: CONSTANT .
$default reduziere mit Regel 9 (comp)
Zustand 3
7 exp: '~' . exp
TOKEN schiebe und gehe zu Zustand 1 über
CONSTANT schiebe und gehe zu Zustand 2 über
'~' schiebe und gehe zu Zustand 3 über
'(' schiebe und gehe zu Zustand 4 über
exp gehe zu Zustand 8 über
comp gehe zu Zustand 7 über
Zustand 4
8 exp: '(' . exp ')'
TOKEN schiebe und gehe zu Zustand 1 über
CONSTANT schiebe und gehe zu Zustand 2 über
'~' schiebe und gehe zu Zustand 3 über
'(' schiebe und gehe zu Zustand 4 über
exp gehe zu Zustand 9 über
comp gehe zu Zustand 7 über
Zustand 5
0 $accept: input . $end
$end schiebe und gehe zu Zustand 10 über
Zustand 6
2 input: exp . '\n'
4 exp: exp . '&' exp
5 | exp . '|' exp
6 | exp . '^' exp
'&' schiebe und gehe zu Zustand 11 über
'|' schiebe und gehe zu Zustand 12 über
'^' schiebe und gehe zu Zustand 13 über
'\n' schiebe und gehe zu Zustand 14 über
Zustand 7
3 exp: comp .
$default reduziere mit Regel 3 (exp)
Zustand 8
4 exp: exp . '&' exp
5 | exp . '|' exp
6 | exp . '^' exp
7 | '~' exp .
$default reduziere mit Regel 7 (exp)
Zustand 9
4 exp: exp . '&' exp
5 | exp . '|' exp
6 | exp . '^' exp
8 | '(' exp . ')'
'&' schiebe und gehe zu Zustand 11 über
'|' schiebe und gehe zu Zustand 12 über
'^' schiebe und gehe zu Zustand 13 über
')' schiebe und gehe zu Zustand 15 über
Zustand 10
0 $accept: input $end .
$default annehmen
Zustand 11
4 exp: exp '&' . exp
TOKEN schiebe und gehe zu Zustand 1 über
CONSTANT schiebe und gehe zu Zustand 2 über
'~' schiebe und gehe zu Zustand 3 über
'(' schiebe und gehe zu Zustand 4 über
exp gehe zu Zustand 16 über
comp gehe zu Zustand 7 über
Zustand 12
5 exp: exp '|' . exp
TOKEN schiebe und gehe zu Zustand 1 über
CONSTANT schiebe und gehe zu Zustand 2 über
'~' schiebe und gehe zu Zustand 3 über
'(' schiebe und gehe zu Zustand 4 über
exp gehe zu Zustand 17 über
comp gehe zu Zustand 7 über
Zustand 13
6 exp: exp '^' . exp
TOKEN schiebe und gehe zu Zustand 1 über
CONSTANT schiebe und gehe zu Zustand 2 über
'~' schiebe und gehe zu Zustand 3 über
'(' schiebe und gehe zu Zustand 4 über
exp gehe zu Zustand 18 über
comp gehe zu Zustand 7 über
Zustand 14
2 input: exp '\n' .
$default reduziere mit Regel 2 (input)
Zustand 15
8 exp: '(' exp ')' .
$default reduziere mit Regel 8 (exp)
Zustand 16
4 exp: exp . '&' exp
4 | exp '&' exp .
5 | exp . '|' exp
6 | exp . '^' exp
$default reduziere mit Regel 4 (exp)
Zustand 17
4 exp: exp . '&' exp
5 | exp . '|' exp
5 | exp '|' exp .
6 | exp . '^' exp
$default reduziere mit Regel 5 (exp)
Zustand 18
4 exp: exp . '&' exp
5 | exp . '|' exp
6 | exp . '^' exp
6 | exp '^' exp .
$default reduziere mit Regel 6 (exp)

1539
lib/boolean/boolean.tab.c Normal file

File diff suppressed because it is too large Load diff

85
lib/boolean/boolean.tab.h Normal file
View file

@ -0,0 +1,85 @@
/* A Bison parser, made by GNU Bison 3.0.4. */
/* Bison interface for Yacc-like parsers in C
Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* As a special exception, you may create a larger work that contains
part or all of the Bison parser skeleton and distribute that work
under terms of your choice, so long as that work isn't itself a
parser generator using the skeleton or a modified version thereof
as a parser skeleton. Alternatively, if you modify or redistribute
the parser skeleton itself, you may (at your option) remove this
special exception, which will cause the skeleton and the resulting
Bison output files to be licensed under the GNU General Public
License without this special exception.
This special exception was added by the Free Software Foundation in
version 2.2 of Bison. */
#ifndef YY_LOG_EXPRESSION_BOOLEAN_TAB_H_INCLUDED
# define YY_LOG_EXPRESSION_BOOLEAN_TAB_H_INCLUDED
/* Debug traces. */
#ifndef LOG_EXPRESSION_DEBUG
# if defined YYDEBUG
#if YYDEBUG
# define LOG_EXPRESSION_DEBUG 1
# else
# define LOG_EXPRESSION_DEBUG 0
# endif
# else /* ! defined YYDEBUG */
# define LOG_EXPRESSION_DEBUG 0
# endif /* ! defined YYDEBUG */
#endif /* ! defined LOG_EXPRESSION_DEBUG */
#if LOG_EXPRESSION_DEBUG
extern int log_expression_debug;
#endif
/* Token type. */
#ifndef LOG_EXPRESSION_TOKENTYPE
# define LOG_EXPRESSION_TOKENTYPE
enum log_expression_tokentype
{
TOKEN = 258,
CONSTANT = 259,
NEG = 260
};
#endif
/* Value type. */
#if ! defined LOG_EXPRESSION_STYPE && ! defined LOG_EXPRESSION_STYPE_IS_DECLARED
union LOG_EXPRESSION_STYPE
{
#line 12 "boolean.y" /* yacc.c:1909 */
char *token;
unsigned long constant;
#line 73 "boolean.tab.h" /* yacc.c:1909 */
};
typedef union LOG_EXPRESSION_STYPE LOG_EXPRESSION_STYPE;
# define LOG_EXPRESSION_STYPE_IS_TRIVIAL 1
# define LOG_EXPRESSION_STYPE_IS_DECLARED 1
#endif
extern LOG_EXPRESSION_STYPE log_expression_lval;
int log_expression_parse (void);
#endif /* !YY_LOG_EXPRESSION_BOOLEAN_TAB_H_INCLUDED */

74
lib/boolean/boolean.y Normal file
View file

@ -0,0 +1,74 @@
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void log_expression_error(const char *s);
int log_expression_lex();
int log_expression_parse();
long lookup_token(char *token);
%}
%union {
char *token;
unsigned long constant;
}
%token <token> TOKEN
%token <constant> CONSTANT
%type <constant> exp comp
%left '&' '|' '^'
%precedence NEG
%define api.prefix {log_expression_}
%%
input:
| exp '\n' { printf("\n\nresult = %#lx\n", $1); }
;
exp :
comp
| exp '&' exp { $$ = $1 & $3; }
| exp '|' exp { $$ = $1 | $3; }
| exp '^' exp { $$ = $1 ^ $3; }
| '~' exp %prec NEG { $$ = ~$2; printf("neg\n"); }
| '(' exp ')' { $$ = $2; printf("subex"); }
;
comp :
CONSTANT { $$ = $1; printf("const %ld", $1); }
| TOKEN { $$ = lookup_token($1); printf("token '%s'", $1); }
;
%%
long lookup_token(char *token)
{
if (!strcmp(token, "a"))
return 201;
else if (!strcmp(token, "b"))
return 202;
if (!strcmp(token, "c"))
return 203;
if (!strcmp(token, "d"))
return 204;
else
return 1111;
}
int main(int argc, char *argv[]) {
do {
log_expression_parse();
} while (!feof(stdin));
}
void log_expression_error(const char *s) {
printf("EEK, parse error! Message: %s", s);
exit(-1);
}

File diff suppressed because it is too large Load diff

332
lib/cfg.c
View file

@ -14,21 +14,22 @@
#include "cfg.h"
#include "node.h"
#include "path.h"
#include "hooks.h"
#include "hook.h"
#include "advio.h"
#include "web.h"
#include "log.h"
#include "api.h"
#include "plugin.h"
#include "node.h"
#include "kernel/rt.h"
int cfg_init_pre(struct cfg *cfg)
{
config_init(cfg->cfg);
config_init(&cfg->cfg);
info("Inititliaze logging sub-system");
log_init(&cfg->log);
log_init(&cfg->log, V, LOG_ALL);
list_init(&cfg->nodes);
list_init(&cfg->paths);
@ -39,12 +40,12 @@ int cfg_init_pre(struct cfg *cfg)
int cfg_init_post(struct cfg *cfg)
{
info("Initialize real-time sub-system");
rt_init(cfg);
info("Initialize memory system");
memory_init();
info("Initialize real-time sub-system");
rt_init(cfg->priority, cfg->affinity);
info("Initialize hook sub-system");
hook_init(cfg);
info("Initialize API sub-system");
api_init(&cfg->api, cfg);
@ -67,12 +68,15 @@ int cfg_deinit(struct cfg *cfg)
info("De-initialize API");
api_deinit(&cfg->api);
info("De-initialize log sub-system");
log_deinit(&cfg->log);
return 0;
}
int cfg_destroy(struct cfg *cfg)
{
config_destroy(cfg->cfg);
config_destroy(&cfg->cfg);
web_destroy(&cfg->web);
log_destroy(&cfg->log);
@ -87,13 +91,13 @@ int cfg_destroy(struct cfg *cfg)
int cfg_parse(struct cfg *cfg, const char *uri)
{
config_setting_t *cfg_root, *cfg_nodes, *cfg_paths, *cfg_plugins, *cfg_logging;
config_setting_t *cfg_root, *cfg_nodes, *cfg_paths, *cfg_plugins, *cfg_logging, *cfg_web;
int ret = CONFIG_FALSE;
if (uri) {
/* Setup libconfig */
config_set_auto_convert(cfg->cfg, 1);
config_set_auto_convert(&cfg->cfg, 1);
FILE *f;
AFILE *af;
@ -110,7 +114,7 @@ int cfg_parse(struct cfg *cfg, const char *uri)
char *uri_cpy = strdup(uri);
char *include_dir = dirname(uri_cpy);
config_set_include_dir(cfg->cfg, include_dir);
config_set_include_dir(&cfg->cfg, include_dir);
free(uri_cpy);
@ -128,9 +132,9 @@ int cfg_parse(struct cfg *cfg, const char *uri)
error("Failed to open configuration from: %s", uri);
/* Parse config */
ret = config_read(cfg->cfg, f);
ret = config_read(&cfg->cfg, f);
if (ret != CONFIG_TRUE)
error("Failed to parse configuration: %s in %s:%d", config_error_text(cfg->cfg), uri, config_error_line(cfg->cfg));
error("Failed to parse configuration: %s in %s:%d", config_error_text(&cfg->cfg), uri, config_error_line(&cfg->cfg));
/* Close configuration file */
if (af)
@ -138,26 +142,37 @@ int cfg_parse(struct cfg *cfg, const char *uri)
else
fclose(f);
}
else
else {
warn("No configuration file specified. Starting unconfigured. Use the API to configure this instance.");
cfg->web.port = 80;
cfg->web.htdocs = "/villas/web/socket/";
}
/* Parse global settings */
cfg_root = config_root_setting(cfg->cfg);
cfg_root = config_root_setting(&cfg->cfg);
if (cfg_root) {
if (!config_setting_is_group(cfg_root))
warn("Missing global section in config file.");
cfg_parse_global(cfg_root, cfg);
if (!config_setting_lookup_int(cfg_root, "affinity", &cfg->affinity))
cfg->affinity = 0;
if (!config_setting_lookup_int(cfg_root, "priority", &cfg->priority))
cfg->priority = 0;
if (!config_setting_lookup_float(cfg_root, "stats", &cfg->stats))
cfg->stats = 0;
}
cfg_web = config_setting_get_member(cfg_root, "http");
if (cfg_web)
web_parse(&cfg->web, cfg_web);
/* Parse logging settings */
cfg_logging = config_setting_get_member(cfg_root, "logging");
if (cfg_logging) {
if (!config_setting_is_group(cfg_logging))
cerror(cfg_logging, "Setting 'logging' must be a group.");
if (cfg_logging)
log_parse(&cfg->log, cfg_logging);
}
/* Parse plugins */
cfg_plugins = config_setting_get_member(cfg_root, "plugins");
@ -174,7 +189,7 @@ int cfg_parse(struct cfg *cfg, const char *uri)
if (ret)
cerror(cfg_plugin, "Failed to parse plugin");
list_push(&cfg->plugins, &plugin);
list_push(&cfg->plugins, memdup(&plugin, sizeof(plugin)));
}
}
@ -186,7 +201,25 @@ int cfg_parse(struct cfg *cfg, const char *uri)
for (int i = 0; i < config_setting_length(cfg_nodes); i++) {
config_setting_t *cfg_node = config_setting_get_elem(cfg_nodes, i);
cfg_parse_node(cfg_node, &cfg->nodes, cfg);
struct node_type *vt;
const char *type;
/* Required settings */
if (!config_setting_lookup_string(cfg_node, "type", &type))
cerror(cfg_node, "Missing node type");
vt = list_lookup(&node_types, type);
if (!vt)
cerror(cfg_node, "Invalid node type: %s", type);
struct node *n = node_create(vt);
ret = node_parse(n, cfg_node);
if (ret)
cerror(cfg_node, "Failed to parse node");
list_push(&cfg->nodes, n);
}
}
@ -198,239 +231,26 @@ int cfg_parse(struct cfg *cfg, const char *uri)
for (int i = 0; i < config_setting_length(cfg_paths); i++) {
config_setting_t *cfg_path = config_setting_get_elem(cfg_paths, i);
cfg_parse_path(cfg_path, &cfg->paths, &cfg->nodes, cfg);
struct path *p = path_create();
ret = path_parse(p, cfg_path, &cfg->nodes);
if (ret)
cerror(cfg_path, "Failed to parse path");
list_push(&cfg->paths, p);
if (p->reverse) {
struct path *r = path_create();
ret = path_reverse(p, r);
if (ret)
cerror(cfg_path, "Failed to reverse path %s", path_name(p));
list_push(&cfg->paths, r);
}
}
}
return 0;
}
int cfg_parse_global(config_setting_t *cfg, struct cfg *set)
{
if (!config_setting_lookup_int(cfg, "affinity", &set->affinity))
set->affinity = 0;
if (!config_setting_lookup_int(cfg, "priority", &set->priority))
set->priority = 0;
if (!config_setting_lookup_float(cfg, "stats", &set->stats))
set->stats = 0;
return 0;
}
int cfg_parse_path(config_setting_t *cfg,
struct list *paths, struct list *nodes, struct cfg *set)
{
config_setting_t *cfg_out, *cfg_hook;
const char *in;
int ret, reverse;
struct path *p;
/* Allocate memory and intialize path structure */
p = alloc(sizeof(struct path));
path_init(p);
/* Input node */
if (!config_setting_lookup_string(cfg, "in", &in) &&
!config_setting_lookup_string(cfg, "from", &in) &&
!config_setting_lookup_string(cfg, "src", &in) &&
!config_setting_lookup_string(cfg, "source", &in))
cerror(cfg, "Missing input node for path");
p->in = list_lookup(nodes, in);
if (!p->in)
cerror(cfg, "Invalid input node '%s'", in);
/* Output node(s) */
if (!(cfg_out = config_setting_get_member(cfg, "out")) &&
!(cfg_out = config_setting_get_member(cfg, "to")) &&
!(cfg_out = config_setting_get_member(cfg, "dst")) &&
!(cfg_out = config_setting_get_member(cfg, "dest")) &&
!(cfg_out = config_setting_get_member(cfg, "sink")))
cerror(cfg, "Missing output nodes for path");
ret = cfg_parse_nodelist(cfg_out, &p->destinations, nodes);
if (ret <= 0)
cerror(cfg_out, "Invalid output nodes");
/* Check if nodes are suitable */
if (p->in->_vt->read == NULL)
cerror(cfg, "Input node '%s' is not supported as a source.", node_name(p->in));
list_foreach(struct node *n, &p->destinations) {
if (n->_vt->write == NULL)
cerror(cfg_out, "Output node '%s' is not supported as a destination.", node_name(n));
}
/* Optional settings */
cfg_hook = config_setting_get_member(cfg, "hook");
if (cfg_hook)
cfg_parse_hooklist(cfg_hook, &p->hooks);
if (!config_setting_lookup_int(cfg, "values", &p->samplelen))
p->samplelen = DEFAULT_VALUES;
if (!config_setting_lookup_int(cfg, "queuelen", &p->queuelen))
p->queuelen = DEFAULT_QUEUELEN;
if (!config_setting_lookup_bool(cfg, "reverse", &reverse))
reverse = 0;
if (!config_setting_lookup_bool(cfg, "enabled", &p->enabled))
p->enabled = 1;
if (!config_setting_lookup_float(cfg, "rate", &p->rate))
p->rate = 0; /* disabled */
p->cfg = cfg;
list_push(paths, p);
if (reverse) {
if (list_length(&p->destinations) > 1)
cerror(cfg, "Can't reverse path with multiple destination nodes");
struct path *r = memdup(p, sizeof(struct path));
path_init(r);
/* Swap source and destination node */
r->in = list_first(&p->destinations);
list_push(&r->destinations, p->in);
if (cfg_hook)
cfg_parse_hooklist(cfg_hook, &r->hooks);
list_push(paths, r);
}
return 0;
}
int cfg_parse_nodelist(config_setting_t *cfg, struct list *list, struct list *all) {
const char *str;
struct node *node;
switch (config_setting_type(cfg)) {
case CONFIG_TYPE_STRING:
str = config_setting_get_string(cfg);
if (str) {
node = list_lookup(all, str);
if (node)
list_push(list, node);
else
cerror(cfg, "Unknown outgoing node '%s'", str);
}
else
cerror(cfg, "Invalid outgoing node");
break;
case CONFIG_TYPE_ARRAY:
for (int i = 0; i < config_setting_length(cfg); i++) {
config_setting_t *elm = config_setting_get_elem(cfg, i);
str = config_setting_get_string(elm);
if (str) {
node = list_lookup(all, str);
if (!node)
cerror(elm, "Unknown outgoing node '%s'", str);
else if (node->_vt->write == NULL)
cerror(cfg, "Output node '%s' is not supported as a sink.", node_name(node));
list_push(list, node);
}
else
cerror(cfg, "Invalid outgoing node");
}
break;
default:
cerror(cfg, "Invalid output node(s)");
}
return list_length(list);
}
int cfg_parse_node(config_setting_t *cfg, struct list *nodes, struct cfg *set)
{
const char *type;
int ret;
struct node *n;
struct node_type *vt;
/* Required settings */
if (!config_setting_lookup_string(cfg, "type", &type))
cerror(cfg, "Missing node type");
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 = config_setting_name(cfg);
n->cfg = cfg;
ret = node_parse(n, cfg);
if (ret)
cerror(cfg, "Failed to parse node '%s'", node_name(n));
if (config_setting_lookup_int(cfg, "vectorize", &n->vectorize)) {
config_setting_t *cfg_vectorize = config_setting_lookup(cfg, "vectorize");
if (n->vectorize <= 0)
cerror(cfg_vectorize, "Invalid value for `vectorize` %d. Must be natural number!", n->vectorize);
if (vt->vectorize && vt->vectorize < n->vectorize)
cerror(cfg_vectorize, "Invalid value for `vectorize`. Node type %s requires a number smaller than %d!",
node_name_type(n), vt->vectorize);
}
else
n->vectorize = 1;
if (!config_setting_lookup_int(cfg, "affinity", &n->affinity))
n->affinity = set->affinity;
list_push(nodes, n);
return ret;
}
int cfg_parse_hooklist(config_setting_t *cfg, struct list *list) {
switch (config_setting_type(cfg)) {
case CONFIG_TYPE_STRING:
cfg_parse_hook(cfg, list);
break;
case CONFIG_TYPE_ARRAY:
for (int i = 0; i < config_setting_length(cfg); i++)
cfg_parse_hook(config_setting_get_elem(cfg, i), list);
break;
default:
cerror(cfg, "Invalid hook functions");
}
return list_length(list);
}
int cfg_parse_hook(config_setting_t *cfg, struct list *list)
{
struct hook *hook, *copy;
char *name, *param;
const char *hookline = config_setting_get_string(cfg);
if (!hookline)
cerror(cfg, "Invalid hook function");
name = strtok((char *) hookline, ":");
param = strtok(NULL, "");
debug(3, "Hook: %s => %s", name, 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;
}
}

View file

@ -17,20 +17,32 @@
#define VAL(h, i) ((h)->low + (i) * (h)->resolution)
#define INDEX(h, v) round((v - (h)->low) / (h)->resolution)
void hist_create(struct hist *h, double low, double high, double resolution)
int hist_create(struct hist *h, double low, double high, double resolution)
{
h->low = low;
h->high = high;
h->resolution = resolution;
h->length = (high - low) / resolution;
h->data = alloc(h->length * sizeof(unsigned));
if (resolution > 0) {
h->length = (high - low) / resolution;
h->data = alloc(h->length * sizeof(hist_cnt_t));
}
else {
h->length = 0;
h->data = NULL;
}
hist_reset(h);
return 0;
}
int hist_destroy(struct hist *h)
{
free(h->data);
if (h->data) {
free(h->data);
h->data = NULL;
}
return 0;
}
@ -52,7 +64,7 @@ void hist_put(struct hist *h, double value)
h->higher++;
else if (idx < 0)
h->lower++;
else
else if (h->data != NULL)
h->data[idx]++;
h->total++;
@ -83,7 +95,8 @@ void hist_reset(struct hist *h)
h->highest = DBL_MIN;
h->lowest = DBL_MAX;
memset(h->data, 0, h->length * sizeof(unsigned));
if (h->data)
memset(h->data, 0, h->length * sizeof(unsigned));
}
double hist_mean(struct hist *h)
@ -101,13 +114,13 @@ double hist_stddev(struct hist *h)
return sqrt(hist_var(h));
}
void hist_print(struct hist *h)
void hist_print(struct hist *h, int details)
{ INDENT
stats("Counted values: %u (%u between %f and %f)", h->total, h->total-h->higher-h->lower, h->high, h->low);
stats("Counted values: %ju (%ju 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) {
if (details > 0 && h->total - h->higher - h->lower > 0) {
char *buf = hist_dump(h);
stats("Matlab: %s", buf);
free(buf);
@ -135,11 +148,11 @@ void hist_plot(struct hist *h)
for (int i = 0; i < h->length; i++) {
double value = VAL(h, i);
int cnt = h->data[i];
hist_cnt_t cnt = h->data[i];
int bar = HIST_HEIGHT * ((double) cnt / max);
if (value > h->lowest || value < h->highest)
stats("%+9g | " "%5u" " | %.*s", value, cnt, bar, buf);
stats("%+9g | %5ju | %.*s", value, cnt, bar, buf);
}
}
@ -150,26 +163,67 @@ char * hist_dump(struct hist *h)
strcatf(&buf, "[ ");
for (int i = 0; i < h->length; i++)
strcatf(&buf, "%u ", h->data[i]);
strcatf(&buf, "%ju ", h->data[i]);
strcatf(&buf, "]");
return buf;
}
void hist_matlab(struct hist *h, FILE *f)
#ifdef WITH_JANSSON
json_t * hist_json(struct hist *h)
{
json_t *b = json_array();
for (int i = 0; i < h->length; i++)
json_array_append(b, json_integer(h->data[i]));
return json_pack("{ s: f, s: f, s: i, s: i, s: i, s: f, s: f, s: f, s: f, s: f, s: o }",
"low", h->low,
"high", h->high,
"total", h->total,
"higher", h->higher,
"lower", h->lower,
"highest", h->highest,
"lowest", h->lowest,
"mean", hist_mean(h),
"variance", hist_var(h),
"stddev", hist_stddev(h),
"buckets", b
);
}
int hist_dump_json(struct hist *h, FILE *f)
{
json_t *j = hist_json(h);
int ret = json_dumpf(j, f, 0);
json_decref(j);
return ret;
}
#endif /* WITH_JANNSON */
int hist_dump_matlab(struct hist *h, FILE *f)
{
char *buf = hist_dump(h);
fprintf(f, "%lu = struct( ", time(NULL));
fprintf(f, "'min', %f, 'max', %f, ", h->low, h->high);
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, "'low', %f, ", h->low);
fprintf(f, "'high', %f, ", h->high);
fprintf(f, "'total', %ju, ", h->total);
fprintf(f, "'higher', %ju, ", h->higher);
fprintf(f, "'lower', %ju, ", h->lower);
fprintf(f, "'highest', %f, ", h->highest);
fprintf(f, "'lowest', %f, ", h->lowest);
fprintf(f, "'mean', %f, ", hist_mean(h));
fprintf(f, "'var', %f, ", hist_var(h));
fprintf(f, "'variance', %f, ", hist_var(h));
fprintf(f, "'stddev', %f, ", hist_stddev(h));
fprintf(f, "'hist', %s ", buf);
fprintf(f, "'buckets', %s ", buf);
fprintf(f, "),\n");
free(buf);
return 0;
}

158
lib/hook.c Normal file
View file

@ -0,0 +1,158 @@
/** Hook-releated functions.
*
* @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 <string.h>
#include <math.h>
#include <libconfig.h>
#include "timing.h"
#include "config.h"
#include "msg.h"
#include "hook.h"
#include "path.h"
#include "utils.h"
#include "node.h"
#include "plugin.h"
int hook_init(struct hook *h, struct cfg *cfg)
{
struct hook_info i = {
.nodes = &cfg->nodes,
.paths = &cfg->paths
};
if (h->type & HOOK_INIT)
return h->cb(h, HOOK_INIT, &i);
else
return 0;
}
int hook_destroy(struct hook *h)
{
struct hook_info i = { NULL };
if (h->type & HOOK_DESTROY)
return h->cb(h, HOOK_DESTROY, &i);
else
return 0;
}
int hook_copy(struct hook *h, struct hook *c)
{
memcpy(c, h, sizeof(struct hook));
c->_vd =
c->prev =
c->last = NULL;
return 0;
}
int hook_cmp_priority(const void *a, const void *b) {
struct hook *ha = (struct hook *) a;
struct hook *hb = (struct hook *) b;
return ha->priority - hb->priority;
}
int hook_run(struct path *p, struct sample *smps[], size_t cnt, int when)
{
struct hook_info i = {
.path = p,
.smps = smps,
.cnt = cnt
};
list_foreach(struct hook *h, &p->hooks) {
if (h->type & when) {
cnt = h->cb(h, when, &i);
debug(LOG_HOOK | 22, "Ran hook '%s' when=%u prio=%u, cnt=%zu", h->name, when, h->priority, cnt);
if (cnt == 0)
break;
}
}
return cnt;
}
void * hook_storage(struct hook *h, int when, size_t len, ctor_cb_t ctor, dtor_cb_t dtor)
{
switch (when) {
case HOOK_INIT:
h->_vd = alloc(len);
if (ctor)
ctor(h->_vd);
break;
case HOOK_DESTROY:
if (dtor)
dtor(h->_vd);
free(h->_vd);
h->_vd = NULL;
break;
}
return h->_vd;
}
/** Parse an array or single hook function.
*
* Examples:
* hooks = [ "print", "fir" ]
* hooks = "log"
*/
int hook_parse_list(struct list *list, config_setting_t *cfg)
{
switch (config_setting_type(cfg)) {
case CONFIG_TYPE_STRING:
hook_parse(cfg, list);
break;
case CONFIG_TYPE_ARRAY:
for (int i = 0; i < config_setting_length(cfg); i++)
hook_parse(config_setting_get_elem(cfg, i), list);
break;
default:
cerror(cfg, "Invalid hook functions");
}
return list_length(list);
}
int hook_parse(config_setting_t *cfg, struct list *list)
{
struct hook *hook;
struct plugin *plg;
char *name, *param;
const char *hookline = config_setting_get_string(cfg);
if (!hookline)
cerror(cfg, "Invalid hook function");
name = strtok((char *) hookline, ":");
param = strtok(NULL, "");
debug(3, "Hook: %s => %s", name, param);
plg = plugin_lookup(PLUGIN_TYPE_HOOK, name);
if (!plg)
cerror(cfg, "Unknown hook function '%s'", name);
hook = memdup(&plg->hook, sizeof(struct hook));
hook->parameter = param;
list_push(list, hook);
return 0;
}

View file

@ -1,64 +0,0 @@
/** Hook-releated functions.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
#include <string.h>
#include <math.h>
#include "timing.h"
#include "config.h"
#include "msg.h"
#include "hooks.h"
#include "path.h"
#include "utils.h"
#include "node.h"
#include "cfg.h"
struct list hooks;
struct cfg *cfg = NULL;
void hook_init(struct cfg *c)
{
cfg = c;
}
int hooks_sort_priority(const void *a, const void *b) {
struct hook *ha = (struct hook *) a;
struct hook *hb = (struct hook *) b;
return ha->priority - hb->priority;
}
int hook_run(struct path *p, struct sample *smps[], size_t cnt, int when)
{
list_foreach(struct hook *h, &p->hooks) {
if (h->type & when) {
debug(LOG_HOOK | 22, "Running hook when=%u '%s' prio=%u, cnt=%zu", when, h->name, h->priority, cnt);
cnt = h->cb(p, h, when, smps, cnt);
if (cnt == 0)
break;
}
}
return cnt;
}
void * hook_storage(struct hook *h, int when, size_t len)
{
switch (when) {
case HOOK_INIT:
h->_vd = alloc(len);
break;
case HOOK_DEINIT:
free(h->_vd);
h->_vd = NULL;
break;
}
return h->_vd;
}

66
lib/hooks/convert.c Normal file
View file

@ -0,0 +1,66 @@
/** Convert hook.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include "hook.h"
#include "plugin.h"
static int hook_convert(struct hook *h, int when, struct hook_info *k)
{
struct {
enum {
TO_FIXED,
TO_FLOAT
} mode;
} *private = hook_storage(h, when, sizeof(*private), NULL, NULL);
switch (when) {
case HOOK_PARSE:
if (!h->parameter)
error("Missing parameter for hook: '%s'", h->name);
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_READ:
for (int i = 0; i < k->cnt; i++) {
for (int j = 0; j < k->smps[0]->length; j++) {
switch (private->mode) {
case TO_FIXED: k->smps[i]->data[j].i = k->smps[i]->data[j].f * 1e3; break;
case TO_FLOAT: k->smps[i]->data[j].f = k->smps[i]->data[j].i; break;
}
}
}
return k->cnt;
}
return 0;
}
static struct plugin p = {
.name = "convert",
.description = "Convert message from / to floating-point / integer",
.type = PLUGIN_TYPE_HOOK,
.hook = {
.priority = 99,
.history = 0,
.cb = hook_convert,
.type = HOOK_STORAGE | HOOK_DESTROY | HOOK_READ
}
};
REGISTER_PLUGIN(&p)
/** @} */

67
lib/hooks/decimate.c Normal file
View file

@ -0,0 +1,67 @@
/** Decimate hook.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include "hook.h"
#include "plugin.h"
static int hook_decimate(struct hook *h, int when, struct hook_info *j)
{
struct {
unsigned ratio;
unsigned counter;
} *private = hook_storage(h, when, sizeof(*private), NULL, NULL);
switch (when) {
case HOOK_PARSE:
if (!h->parameter)
error("Missing parameter for hook: '%s'", h->name);
private->ratio = strtol(h->parameter, NULL, 10);
if (!private->ratio)
error("Invalid parameter '%s' for hook 'decimate'", h->parameter);
private->counter = 0;
break;
case HOOK_READ:
assert(j->smps);
int i, ok;
for (i = 0, ok = 0; i < j->cnt; i++) {
if (private->counter++ % private->ratio == 0) {
struct sample *tmp;
tmp = j->smps[ok];
j->smps[ok++] = j->smps[i];
j->smps[i] = tmp;
}
}
return ok;
}
return 0;
}
static struct plugin p = {
.name = "decimate",
.description = "Downsamping by integer factor",
.type = PLUGIN_TYPE_HOOK,
.hook = {
.priority = 99,
.history = 0,
.cb = hook_decimate,
.type = HOOK_STORAGE | HOOK_DESTROY | HOOK_READ
}
};
REGISTER_PLUGIN(&p)
/** @} */

74
lib/hooks/drop.c Normal file
View file

@ -0,0 +1,74 @@
/** Drop hook.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include "hook.h"
#include "plugin.h"
#include "stats.h"
#include "path.h"
static int hook_drop(struct hook *h, int when, struct hook_info *j)
{
int i, ok, dist;
assert(j->smps);
for (i = 0, ok = 0; i < j->cnt; i++) {
h->last = j->smps[i];
if (h->prev) {
dist = h->last->sequence - (int32_t) h->prev->sequence;
if (dist <= 0) {
warn("Dropped sample: dist = %d, i = %d", dist, i);
if (j->path && j->path->stats)
stats_update(j->path->stats->delta, STATS_DROPPED, dist);
}
else {
struct sample *tmp;
tmp = j->smps[i];
j->smps[i] = j->smps[ok];
j->smps[ok++] = tmp;
}
/* To discard the first X samples in 'smps[]' we must
* shift them to the end of the 'smps[]' array.
* In case the hook returns a number 'ok' which is smaller than 'cnt',
* only the first 'ok' samples in 'smps[]' are accepted and further processed.
*/
}
else {
struct sample *tmp;
tmp = j->smps[i];
j->smps[i] = j->smps[ok];
j->smps[ok++] = tmp;
}
h->prev = h->last;
}
return ok;
}
static struct plugin p = {
.name = "drop",
.description = "Drop messages with reordered sequence numbers",
.type = PLUGIN_TYPE_HOOK,
.hook = {
.priority = 3,
.history = 1,
.cb = hook_drop,
.type = HOOK_AUTO | HOOK_READ
}
};
REGISTER_PLUGIN(&p)
/** @} */

54
lib/hooks/fix_ts.c Normal file
View file

@ -0,0 +1,54 @@
/** Fix timestamp hook.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include "hook.h"
#include "plugin.h"
#include "timing.h"
int hook_fix_ts(struct hook *h, int when, struct hook_info *j)
{
struct timespec now = time_now();
assert(j->smps);
for (int i = 0; i < j->cnt; i++) {
/* Check for missing receive timestamp
* Usually node_type::read() should update the receive timestamp.
* An example would be to use hardware timestamp capabilities of
* modern NICs.
*/
if ((j->smps[i]->ts.received.tv_sec == 0 && j->smps[i]->ts.received.tv_nsec == 0) ||
(j->smps[i]->ts.received.tv_sec == -1 && j->smps[i]->ts.received.tv_nsec == -1))
j->smps[i]->ts.received = now;
/* Check for missing origin timestamp */
if ((j->smps[i]->ts.origin.tv_sec == 0 && j->smps[i]->ts.origin.tv_nsec == 0) ||
(j->smps[i]->ts.origin.tv_sec == -1 && j->smps[i]->ts.origin.tv_nsec == -1))
j->smps[i]->ts.origin = now;
}
return j->cnt;
}
static struct plugin p = {
.name = "fix_ts",
.description = "Update timestamps of sample if not set",
.type = PLUGIN_TYPE_HOOK,
.hook = {
.priority = 0,
.history = 0,
.cb = hook_fix_ts,
.type = HOOK_AUTO | HOOK_READ
}
};
REGISTER_PLUGIN(&p)
/** @} */

View file

@ -1,103 +0,0 @@
/** Internal hook functions.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
#include "hooks.h"
#include "timing.h"
#include "sample.h"
#include "path.h"
#include "utils.h"
REGISTER_HOOK("fix_ts", "Update timestamps of sample if not set", 0, 0, hook_fix_ts, HOOK_INTERNAL | HOOK_READ)
int hook_fix_ts(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
{
struct timespec now = time_now();
for (int i = 0; i < cnt; i++) {
/* Check for missing receive timestamp
* Usually node_type::read() should update the receive timestamp.
* An example would be to use hardware timestamp capabilities of
* modern NICs.
*/
if ((smps[i]->ts.received.tv_sec == 0 && smps[i]->ts.received.tv_nsec == 0) ||
(smps[i]->ts.received.tv_sec == -1 && smps[i]->ts.received.tv_nsec == -1))
smps[i]->ts.received = now;
/* Check for missing origin timestamp */
if ((smps[i]->ts.origin.tv_sec == 0 && smps[i]->ts.origin.tv_nsec == 0) ||
(smps[i]->ts.origin.tv_sec == -1 && smps[i]->ts.origin.tv_nsec == -1))
smps[i]->ts.origin = now;
}
return cnt;
}
REGISTER_HOOK("restart", "Call restart hooks for current path", 1, 1, hook_restart, HOOK_INTERNAL | HOOK_READ)
int hook_restart(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
{
for (int i = 0; i < cnt; i++) {
h->last = smps[i];
if (h->prev) {
if (h->last->sequence == 0 &&
h->prev->sequence <= UINT32_MAX - 32) {
warn("Simulation for path %s restarted (prev->seq=%u, current->seq=%u)",
path_name(p), h->prev->sequence, h->last->sequence);
p->invalid =
p->skipped =
p->dropped = 0;
hook_run(p, &smps[i], cnt - i, HOOK_PATH_RESTART);
}
}
h->prev = h->last;
}
return cnt;
}
REGISTER_HOOK("drop", "Drop messages with reordered sequence numbers", 3, 1, hook_drop, HOOK_INTERNAL | HOOK_READ)
int hook_drop(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
{
int i, ok, dist;
for (i = 0, ok = 0; i < cnt; i++) {
h->last = smps[i];
if (h->prev) {
dist = h->last->sequence - (int32_t) h->prev->sequence;
if (dist <= 0) {
p->dropped++;
warn("Dropped sample: dist = %d, i = %d", dist, i);
}
else {
struct sample *tmp;
tmp = smps[i];
smps[i] = smps[ok];
smps[ok++] = tmp;
}
/* To discard the first X samples in 'smps[]' we must
* shift them to the end of the 'smps[]' array.
* In case the hook returns a number 'ok' which is smaller than 'cnt',
* only the first 'ok' samples in 'smps[]' are accepted and further processed.
*/
}
else {
struct sample *tmp;
tmp = smps[i];
smps[i] = smps[ok];
smps[ok++] = tmp;
}
h->prev = h->last;
}
return ok;
}

View file

@ -1,163 +0,0 @@
/** Other hook funktions.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "hooks.h"
#include "timing.h"
#include "utils.h"
#include "sample.h"
REGISTER_HOOK("print", "Print the message to stdout", 99, 0, hook_print, HOOK_READ)
int hook_print(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
{
for (int i = 0; i < cnt; i++)
sample_fprint(stdout, smps[i], SAMPLE_ALL);
return cnt;
}
REGISTER_HOOK("ts", "Update timestamp of message with current time", 99, 0, hook_ts, HOOK_READ)
int hook_ts(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
{
for (int i = 0; i < cnt; i++)
smps[i]->ts.origin = smps[i]->ts.received;
return cnt;
}
REGISTER_HOOK("convert", "Convert message from / to floating-point / integer", 99, 0, hook_convert, HOOK_STORAGE | HOOK_PARSE | HOOK_READ)
int hook_convert(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
{
struct {
enum {
TO_FIXED,
TO_FLOAT
} mode;
} *private = hook_storage(h, when, sizeof(*private));
switch (when) {
case HOOK_PARSE:
if (!h->parameter)
error("Missing parameter for hook: '%s'", h->name);
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_READ:
for (int i = 0; i < cnt; i++) {
for (int j = 0; j < smps[0]->length; j++) {
switch (private->mode) {
case TO_FIXED: smps[i]->data[j].i = smps[i]->data[j].f * 1e3; break;
case TO_FLOAT: smps[i]->data[j].f = smps[i]->data[j].i; break;
}
}
}
break;
}
return 0;
}
REGISTER_HOOK("decimate", "Downsamping by integer factor", 99, 0, hook_decimate, HOOK_STORAGE | HOOK_PARSE | HOOK_READ)
int hook_decimate(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
{
struct {
unsigned ratio;
unsigned counter;
} *private = hook_storage(h, when, sizeof(*private));
int ok;
switch (when) {
case HOOK_PARSE:
if (!h->parameter)
error("Missing parameter for hook: '%s'", h->name);
private->ratio = strtol(h->parameter, NULL, 10);
if (!private->ratio)
error("Invalid parameter '%s' for hook 'decimate'", h->parameter);
private->counter = 0;
break;
case HOOK_READ:
ok = 0;
for (int i = 0; i < cnt; i++) {
if (private->counter++ % private->ratio == 0) {
struct sample *tmp;
tmp = smps[ok];
smps[ok++] = smps[i];
smps[i] = tmp;
}
}
return ok;
}
return cnt;
}
REGISTER_HOOK("skip_first", "Skip the first samples", 99, 0, hook_skip_first, HOOK_STORAGE | HOOK_PARSE | HOOK_READ | HOOK_PATH)
int hook_skip_first(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
{
struct {
struct timespec skip; /**< Time to wait until first message is not skipped */
struct timespec until; /**< Absolute point in time from where we accept samples. */
} *private = hook_storage(h, when, sizeof(*private));
char *endptr;
double wait;
int i, ok;
switch (when) {
case HOOK_PARSE:
if (!h->parameter)
error("Missing parameter for hook: '%s'", h->name);
wait = strtof(h->parameter, &endptr);
if (h->parameter == endptr)
error("Invalid parameter '%s' for hook 'skip_first'", h->parameter);
private->skip = time_from_double(wait);
break;
case HOOK_PATH_RESTART:
case HOOK_PATH_STOP:
private->until = time_add(&smps[0]->ts.received, &private->skip);
break;
case HOOK_READ:
for (i = 0, ok = 0; i < cnt; i++) {
if (time_delta(&private->until, &smps[i]->ts.received) > 0) {
struct sample *tmp;
tmp = smps[i];
smps[i] = smps[ok];
smps[ok++] = tmp;
}
/* To discard the first X samples in 'smps[]' we must
* shift them to the end of the 'smps[]' array.
* In case the hook returns a number 'ok' which is smaller than 'cnt',
* only the first 'ok' samples in 'smps[]' are accepted and further processed.
*/
}
return ok;
}
return 0;
}

View file

@ -1,132 +0,0 @@
/** Statistic-related hook functions.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
#include "hooks.h"
#include "sample.h"
#include "path.h"
#include "utils.h"
#include "timing.h"
void hook_stats_header()
{
#define UNIT(u) "(" YEL(u) ")"
stats("%-40s|%19s|%19s|%19s|%19s|%19s|%10s|", "Source " MAG("=>") " Destination",
"OWD" UNIT("S") " ",
"Rate" UNIT("p/S") " ",
"Drop" UNIT("p") " ",
"Skip" UNIT("p") " ",
"Inval" UNIT("p") " ",
"Overuns "
);
line();
}
REGISTER_HOOK("stats", "Collect statistics for the current path", 2, 1, hook_stats, HOOK_STATS)
int hook_stats(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
{
switch (when) {
case HOOK_INIT:
/** @todo Allow configurable bounds for histograms */
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);
hist_create(&p->hist.gap_seq, -HIST_SEQ, +HIST_SEQ, 1);
break;
case HOOK_READ:
for (int i = 0; i < cnt; i++) {
h->last = smps[i];
if (h->prev) {
int gap_seq = h->last->sequence - (int32_t) h->prev->sequence;
double owd = time_delta(&h->last->ts.origin, &h->last->ts.received);
double gap = time_delta(&h->prev->ts.origin, &h->last->ts.origin);
double gap_recv = time_delta(&h->prev->ts.received, &h->last->ts.received);
hist_put(&p->hist.gap_msg, gap);
hist_put(&p->hist.gap_seq, gap_seq);
hist_put(&p->hist.owd, owd);
hist_put(&p->hist.gap_recv, gap_recv);
}
h->prev = h->last;
}
break;
case HOOK_PATH_STOP:
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_DEINIT:
hist_destroy(&p->hist.owd);
hist_destroy(&p->hist.gap_msg);
hist_destroy(&p->hist.gap_recv);
hist_destroy(&p->hist.gap_seq);
break;
case HOOK_PATH_RESTART:
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:
stats("%-40.40s|%10s|%10s|%10ju|%10ju|%10ju|%10ju|", path_name(p), "", "",
p->dropped, p->skipped, p->invalid, p->overrun);
break;
}
return cnt;
}
REGISTER_HOOK("stats_send", "Send path statistics to another node", 99, 0, hook_stats_send, HOOK_STORAGE | HOOK_PARSE | HOOK_PERIODIC | HOOK_PATH)
int hook_stats_send(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
{
struct private {
struct node *dest;
int ratio;
} *private = hook_storage(h, when, sizeof(*private));
switch (when) {
case HOOK_PARSE:
if (!h->parameter)
error("Missing parameter for hook '%s'", h->name);
private->dest = list_lookup(NULL, h->parameter);
if (!private->dest)
error("Invalid destination node '%s' for hook '%s'", h->parameter, h->name);
node_start(private->dest);
break;
case HOOK_PERIODIC: {
int i;
char buf[SAMPLE_LEN(16)];
struct sample *smp = (struct sample *) buf;
i = 0;
smp->data[i++].f = p->invalid;
smp->data[i++].f = p->skipped;
smp->data[i++].f = p->dropped;
smp->data[i++].f = p->overrun;
smp->data[i++].f = p->hist.owd.last,
smp->data[i++].f = 1.0 / p->hist.gap_msg.last;
smp->data[i++].f = 1.0 / p->hist.gap_recv.last;
smp->length = i;
node_write(private->dest, &smp, 1); /* Send single message with statistics to destination node */
break;
}
}
return 0;
}

39
lib/hooks/print.c Normal file
View file

@ -0,0 +1,39 @@
/** Print hook.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include "hook.h"
#include "plugin.h"
#include "sample.h"
static int hook_print(struct hook *h, int when, struct hook_info *j)
{
assert(j->smps);
for (int i = 0; i < j->cnt; i++)
sample_fprint(stdout, j->smps[i], SAMPLE_ALL);
return j->cnt;
}
static struct plugin p = {
.name = "print",
.description = "Print the message to stdout",
.type = PLUGIN_TYPE_HOOK,
.hook = {
.priority = 99,
.history = 0,
.cb = hook_print,
.type = HOOK_READ
}
};
REGISTER_PLUGIN(&p)
/** @} */

53
lib/hooks/restart.c Normal file
View file

@ -0,0 +1,53 @@
/** Path restart hook.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include "hook.h"
#include "plugin.h"
#include "path.h"
static int hook_restart(struct hook *h, int when, struct hook_info *j)
{
assert(j->smps);
assert(j->path);
for (int i = 0; i < j->cnt; i++) {
h->last = j->smps[i];
if (h->prev) {
if (h->last->sequence == 0 &&
h->prev->sequence <= UINT32_MAX - 32) {
warn("Simulation for path %s restarted (prev->seq=%u, current->seq=%u)",
path_name(j->path), h->prev->sequence, h->last->sequence);
hook_run(j->path, &j->smps[i], j->cnt - i, HOOK_PATH_RESTART);
}
}
h->prev = h->last;
}
return j->cnt;
}
static struct plugin p = {
.name = "restart",
.description = "Call restart hooks for current path",
.type = PLUGIN_TYPE_HOOK,
.hook = {
.priority = 1,
.history = 1,
.cb = hook_restart,
.type = HOOK_AUTO | HOOK_READ
}
};
REGISTER_PLUGIN(&p)
/** @} */

83
lib/hooks/skip_first.c Normal file
View file

@ -0,0 +1,83 @@
/** Skip first hook.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include "hook.h"
#include "plugin.h"
#include "timing.h"
static int hook_skip_first(struct hook *h, int when, struct hook_info *j)
{
struct {
struct timespec skip; /**< Time to wait until first message is not skipped */
struct timespec until; /**< Absolute point in time from where we accept samples. */
} *private = hook_storage(h, when, sizeof(*private), NULL, NULL);
char *endptr;
double wait;
switch (when) {
case HOOK_PARSE:
if (!h->parameter)
error("Missing parameter for hook: '%s'", h->name);
wait = strtof(h->parameter, &endptr);
if (h->parameter == endptr)
error("Invalid parameter '%s' for hook 'skip_first'", h->parameter);
private->skip = time_from_double(wait);
break;
case HOOK_PATH_START:
case HOOK_PATH_RESTART:
private->until = time_add(&j->smps[0]->ts.received, &private->skip);
break;
case HOOK_READ:
assert(j->smps);
int i, ok;
for (i = 0, ok = 0; i < j->cnt; i++) {
if (time_delta(&private->until, &j->smps[i]->ts.received) > 0) {
struct sample *tmp;
tmp = j->smps[i];
j->smps[i] = j->smps[ok];
j->smps[ok++] = tmp;
}
/* To discard the first X samples in 'smps[]' we must
* shift them to the end of the 'smps[]' array.
* In case the hook returns a number 'ok' which is smaller than 'cnt',
* only the first 'ok' samples in 'smps[]' are accepted and further processed.
*/
}
return ok;
}
return 0;
}
static struct plugin p = {
.name = "skip_first",
.description = "Skip the first samples",
.type = PLUGIN_TYPE_HOOK,
.hook = {
.priority = 99,
.history = 0,
.cb = hook_skip_first,
.type = HOOK_STORAGE | HOOK_PARSE | HOOK_READ | HOOK_PATH
}
};
REGISTER_PLUGIN(&p)
/** @} */

116
lib/hooks/stats.c Normal file
View file

@ -0,0 +1,116 @@
/** Statistic hooks.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include "hook.h"
#include "plugin.h"
#include "stats.h"
#include "path.h"
static int hook_stats(struct hook *h, int when, struct hook_info *j)
{
struct stats *s = hook_storage(h, when, sizeof(struct stats), (ctor_cb_t) stats_init, (dtor_cb_t) stats_destroy);
switch (when) {
case HOOK_INIT:
if (j->path)
j->path->stats = s;
break;
case HOOK_READ:
assert(j->smps);
stats_collect(s->delta, j->smps, j->cnt);
stats_commit(s, s->delta);
break;
case HOOK_PATH_STOP:
stats_print(s, 1);
break;
case HOOK_PATH_RESTART:
stats_reset(s);
break;
case HOOK_PERIODIC:
assert(j->path);
stats_print_periodic(s, j->path);
break;
}
return j->cnt;
}
/** @todo This is untested */
static int hook_stats_send(struct hook *h, int when, struct hook_info *j)
{
struct private {
struct node *dest;
struct stats *stats;
int ratio;
} *private = hook_storage(h, when, sizeof(*private), NULL, NULL);
switch (when) {
case HOOK_INIT:
assert(j->nodes);
assert(j->path);
if (!h->parameter)
error("Missing parameter for hook '%s'", h->name);
private->dest = list_lookup(j->nodes, h->parameter);
if (!private->dest)
error("Invalid destination node '%s' for hook '%s'", h->parameter, h->name);
break;
case HOOK_PATH_START:
node_start(private->dest);
break;
case HOOK_PATH_STOP:
node_stop(private->dest);
break;
case HOOK_READ:
stats_send(private->stats, private->dest);
break;
}
return 0;
}
static struct plugin p1 = {
.name = "stats",
.description = "Collect statistics for the current path",
.type = PLUGIN_TYPE_HOOK,
.hook = {
.priority = 2,
.history = 1,
.cb = hook_stats,
.type = HOOK_STORAGE | HOOK_PATH | HOOK_READ | HOOK_PERIODIC
}
};
static struct plugin p2 = {
.name = "stats_send",
.description = "Send path statistics to another node",
.type = PLUGIN_TYPE_HOOK,
.hook = {
.priority = 99,
.history = 0,
.cb = hook_stats_send,
.type = HOOK_STORAGE | HOOK_PATH | HOOK_READ
}
};
REGISTER_PLUGIN(&p1)
REGISTER_PLUGIN(&p2)
/** @} */

39
lib/hooks/ts.c Normal file
View file

@ -0,0 +1,39 @@
/** Timestamp hook.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include "hook.h"
#include "plugin.h"
#include "timing.h"
static int hook_ts(struct hook *h, int when, struct hook_info *j)
{
assert(j->smps);
for (int i = 0; i < j->cnt; i++)
j->smps[i]->ts.origin = j->smps[i]->ts.received;
return j->cnt;
}
static struct plugin p = {
.name = "ts",
.description = "Update timestamp of message with current time",
.type = PLUGIN_TYPE_HOOK,
.hook = {
.priority = 99,
.history = 0,
.cb = hook_ts,
.type = HOOK_READ
}
};
REGISTER_PLUGIN(&p)
/** @} */

View file

@ -145,6 +145,38 @@ int kernel_get_cacheline_size()
return sysconf(_SC_LEVEL1_ICACHE_LINESIZE);
}
int kernel_get_nr_hugepages()
{
FILE *f;
int nr, ret;
f = fopen(PROCFS_PATH "/sys/vm/nr_hugepages", "r");
if (!f)
serror("Failed to open %s", PROCFS_PATH "/sys/vm/nr_hugepages");
ret = fscanf(f, "%d", &nr);
if (ret != 1)
nr = -1;
fclose(f);
return nr;
}
int kernel_set_nr_hugepages(int nr)
{
FILE *f;
f = fopen(PROCFS_PATH "/sys/vm/nr_hugepages", "w");
if (!f)
serror("Failed to open %s", PROCFS_PATH "/sys/vm/nr_hugepages");
fprintf(f, "%d\n", nr);
fclose(f);
return 0;
}
#if 0
int kernel_check_cap(cap_value_t cap)
{

View file

@ -14,7 +14,7 @@
#include "kernel/kernel.h"
#include "kernel/rt.h"
int rt_init(struct cfg *cfg)
int rt_init(int priority, int affinity)
{ INDENT
int is_rt;
@ -23,13 +23,13 @@ int rt_init(struct cfg *cfg)
if (is_rt)
warn("We recommend to use an PREEMPT_RT patched kernel!");
if (cfg->priority)
rt_set_priority(cfg->priority);
if (priority)
rt_set_priority(priority);
else
warn("You might want to use the 'priority' setting to increase VILLASnode's process priority");
if (cfg->affinity)
rt_set_affinity(cfg->affinity);
if (affinity)
rt_set_affinity(affinity);
else
warn("You should use the 'affinity' setting to pin VILLASnode to dedicate CPU cores");

View file

@ -21,6 +21,7 @@
#include "OpalPrint.h"
#endif
/** The global log instance. */
static struct log *log;
/** List of debug facilities as strings */
@ -102,17 +103,22 @@ int log_set_facility_expression(struct log *l, const char *expression)
return l->facilities;
}
int log_init(struct log *l)
int log_init(struct log *l, int level, long facilitites)
{
l->epoch = time_now();
l->level = V;
l->facilities = LOG_ALL;
l->level = level;
l->facilities = facilitites;
debug(LOG_LOG | 10, "Log sub-system intialized");
/* Register this log instance globally */
log = l;
debug(LOG_LOG | 10, "Log sub-system intialized");
return 0;
}
int log_deinit(struct log *l)
{
return 0;
}
@ -163,6 +169,9 @@ void log_vprint(struct log *l, const char *lvl, const char *fmt, va_list ap)
int log_parse(struct log *l, config_setting_t *cfg)
{
const char *facilities;
if (!config_setting_is_group(cfg))
cerror(cfg, "Setting 'logging' must be a group.");
config_setting_lookup_int(cfg, "level", &l->level);

View file

@ -10,22 +10,42 @@
/* Required to allocate hugepages on Apple OS X */
#ifdef __MACH__
#include <mach/vm_statistics.h>
#elif defined(__linux__)
#include "kernel/kernel.h"
#endif
#include "log.h"
#include "memory.h"
int memory_init()
{
#ifdef __linux__
int nr = kernel_get_nr_hugepages();
debug(LOG_MEM | 2, "System has %d reserved hugepages", nr);
if (nr < DEFAULT_NR_HUGEPAGES)
kernel_set_nr_hugepages(DEFAULT_NR_HUGEPAGES);
#endif
return 0;
}
void * memory_alloc(const struct memtype *m, size_t len)
{
debug(LOG_MEM | 2, "Allocating %#zx bytes of %s memory", len, m->name);
return m->alloc(len);
void *ptr = m->alloc(len, sizeof(void *));
debug(LOG_MEM | 2, "Allocated %#zx bytes of %s memory: %p", len, m->name, ptr);
return ptr;
}
void * memory_alloc_aligned(const struct memtype *m, size_t len, size_t alignment)
{
debug(LOG_MEM | 2, "Allocating %#zx bytes of %#zx-byte-aligned %s memory", len, alignment, m->name);
warn("%s: not implemented yet!", __FUNCTION__);
return memory_alloc(m, len);
void *ptr = m->alloc(len, alignment);
debug(LOG_MEM | 2, "Allocated %#zx bytes of %#zx-byte-aligned %s memory: %p", len, alignment, m->name, ptr);
return ptr;
}
int memory_free(const struct memtype *m, void *ptr, size_t len)
@ -34,9 +54,17 @@ int memory_free(const struct memtype *m, void *ptr, size_t len)
return m->free(ptr, len);
}
static void * memory_heap_alloc(size_t len)
static void * memory_heap_alloc(size_t len, size_t alignment)
{
return malloc(len);
void *ptr;
int ret;
if (alignment < sizeof(void *))
alignment = sizeof(void *);
ret = posix_memalign(&ptr, alignment, len);
return ret ? NULL : ptr;
}
int memory_heap_free(void *ptr, size_t len)
@ -47,7 +75,7 @@ int memory_heap_free(void *ptr, size_t len)
}
/** Allocate memory backed by hugepages with malloc() like interface */
static void * memory_hugepage_alloc(size_t len)
static void * memory_hugepage_alloc(size_t len, size_t alignment)
{
int prot = PROT_READ | PROT_WRITE;
int flags = MAP_PRIVATE | MAP_ANONYMOUS;

View file

@ -5,20 +5,18 @@
*********************************************************************************/
#include <string.h>
#include <libconfig.h>
#include "sample.h"
#include "node.h"
#include "cfg.h"
#include "utils.h"
#include "config.h"
/** List of registered node-types */
struct list node_types = LIST_INIT();
int node_parse(struct node *n, config_setting_t *cfg)
{
return n->_vt->parse ? n->_vt->parse(n, cfg) : 0;
}
int node_read(struct node *n, struct sample *smps[], unsigned cnt)
{
int nread = 0;
@ -36,6 +34,9 @@ int node_read(struct node *n, struct sample *smps[], unsigned cnt)
nread = n->_vt->read(n, smps, cnt);
}
for (int i = 0; i < nread; i++)
smps[i]->source = n;
return nread;
}
@ -204,3 +205,95 @@ int node_destroy(struct node *n)
return 0;
}
/** Parse an array or single node and checks if they exist in the "nodes" section.
*
* Examples:
* out = [ "sintef", "scedu" ]
* out = "acs"
*
* @param cfg The libconfig object handle for "out".
* @param nodes The nodes will be added to this list.
* @param all This list contains all valid nodes.
*/
int node_parse_list(struct list *list, config_setting_t *cfg, struct list *all) {
const char *str;
struct node *node;
switch (config_setting_type(cfg)) {
case CONFIG_TYPE_STRING:
str = config_setting_get_string(cfg);
if (str) {
node = list_lookup(all, str);
if (node)
list_push(list, node);
else
cerror(cfg, "Unknown outgoing node '%s'", str);
}
else
cerror(cfg, "Invalid outgoing node");
break;
case CONFIG_TYPE_ARRAY:
for (int i = 0; i < config_setting_length(cfg); i++) {
config_setting_t *elm = config_setting_get_elem(cfg, i);
str = config_setting_get_string(elm);
if (str) {
node = list_lookup(all, str);
if (!node)
cerror(elm, "Unknown outgoing node '%s'", str);
else if (node->_vt->write == NULL)
cerror(cfg, "Output node '%s' is not supported as a sink.", node_name(node));
list_push(list, node);
}
else
cerror(cfg, "Invalid outgoing node");
}
break;
default:
cerror(cfg, "Invalid output node(s)");
}
return list_length(list);
}
int node_parse(struct node *n, config_setting_t *cfg)
{
struct node_type *vt;
const char *type, *name;
int ret;
name = config_setting_name(cfg);
if (!config_setting_lookup_string(cfg, "type", &type))
cerror(cfg, "Missing node type");
vt = list_lookup(&node_types, type);
if (!vt)
cerror(cfg, "Invalid type for node '%s'", config_setting_name(cfg));
n->name = name;
n->cfg = cfg;
ret = n->_vt->parse ? n->_vt->parse(n, cfg) : 0;
if (ret)
cerror(cfg, "Failed to parse node '%s'", node_name(n));
if (config_setting_lookup_int(cfg, "vectorize", &n->vectorize)) {
config_setting_t *cfg_vectorize = config_setting_lookup(cfg, "vectorize");
if (n->vectorize <= 0)
cerror(cfg_vectorize, "Invalid value for `vectorize` %d. Must be natural number!", n->vectorize);
if (vt->vectorize && vt->vectorize < n->vectorize)
cerror(cfg_vectorize, "Invalid value for `vectorize`. Node type %s requires a number smaller than %d!",
node_name_type(n), vt->vectorize);
}
else
n->vectorize = 1;
return ret;
}

View file

@ -10,22 +10,307 @@
#include <string.h>
#include <signal.h>
#include <libwebsockets.h>
#include <libconfig.h>
#include "nodes/websocket.h"
#include "webmsg_format.h"
#include "timing.h"
#include "utils.h"
#include "msg.h"
#include "cfg.h"
#include "config.h"
/* Internal datastructures */
struct destination {
char *uri;
struct lws_client_connect_info info;
};
/* Private static storage */
static config_setting_t *cfg_root; /**< Root config */
static pthread_t thread; /**< All nodes are served by a single websocket server. This server is running in a dedicated thread. */
static struct lws_context *context; /**< The libwebsockets server context. */
static int port; /**< Port of the build in HTTP / WebSocket server */
static const char *ssl_cert; /**< Path to the SSL certitifcate for HTTPS / WSS */
static const char *ssl_private_key; /**< Path to the SSL private key for HTTPS / WSS */
static const char *htdocs; /**< Path to the directory which should be served by build in HTTP server */
static int id = 0;
struct list connections; /**< List of active libwebsocket connections which receive samples from all nodes (catch all) */
/* Forward declarations */
static struct node_type vt;
static int protocol_cb_http(struct lws *, enum lws_callback_reasons, void *, void *, size_t);
static int protocol_cb_live(struct lws *, enum lws_callback_reasons, void *, void *, size_t);
int websocket_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
static struct lws_protocols protocols[] = {
{ "http-only", protocol_cb_http, 0, 0 },
{ "live", protocol_cb_live, sizeof(struct connection), 0 },
{ NULL }
};
__attribute__((unused)) static int connection_init(struct connection *c)
{
struct websocket_connection *c = user;
/** @todo */
return -1;
}
__attribute__((unused)) static void connection_destroy(struct connection *c)
{
if (c->_name)
free(c->_name);
}
static char * connection_name(struct connection *c)
{
if (!c->_name) {
if (c->node)
asprintf(&c->_name, "%s (%s) for node %s", c->peer.name, c->peer.ip, node_name(c->node));
else
asprintf(&c->_name, "%s (%s) for all nodes", c->peer.name, c->peer.ip);
}
return c->_name;
}
static void destination_destroy(struct destination *d)
{
free(d->uri);
}
static int connection_write(struct connection *c, struct sample *smps[], unsigned cnt)
{
int blocks, enqueued;
char *bufs[cnt];
struct websocket *w = c->node->_vd;
switch (c->state) {
case WEBSOCKET_SHUTDOWN:
return -1;
case WEBSOCKET_CLOSED:
if (c->node) {
struct websocket *w = (struct websocket *) c->node->_vd;
list_remove(&w->connections, c);
}
else
list_remove(&connections, c);
break;
case WEBSOCKET_ESTABLISHED:
c->state = WEBSOCKET_ACTIVE;
/* fall through */
case WEBSOCKET_ACTIVE:
blocks = pool_get_many(&w->pool, (void **) bufs, cnt);
if (blocks != cnt)
warn("Pool underrun in websocket connection: %s", connection_name(c));
for (int i = 0; i < blocks; i++) {
struct webmsg *msg = (struct webmsg *) (bufs[i] + LWS_PRE);
msg->version = WEBMSG_VERSION;
msg->type = WEBMSG_TYPE_DATA;
msg->endian = WEBMSG_ENDIAN_HOST;
msg->length = smps[i]->length;
msg->sequence = smps[i]->sequence;
msg->id = w->id;
msg->ts.sec = smps[i]->ts.origin.tv_sec;
msg->ts.nsec = smps[i]->ts.origin.tv_nsec;
memcpy(&msg->data, &smps[i]->data, smps[i]->length * 4);
}
enqueued = queue_push_many(&c->queue, (void **) bufs, cnt);
if (enqueued != blocks)
warn("Queue overrun in websocket connection: %s", connection_name(c));
lws_callback_on_writable(c->wsi);
break;
}
return 0;
}
static void logger(int level, const char *msg) {
int len = strlen(msg);
if (strchr(msg, '\n'))
len -= 1;
/* Decrease severity for some errors. */
if (strstr(msg, "Unable to open") == msg)
level = LLL_WARN;
switch (level) {
case LLL_ERR: warn("LWS: %.*s", len, msg); break;
case LLL_WARN: warn("LWS: %.*s", len, msg); break;
case LLL_INFO: info("LWS: %.*s", len, msg); break;
default: debug(LOG_WEBSOCKET | 1, "LWS: %.*s", len, msg); break;
}
}
static void * server_thread(void *ctx)
{
debug(LOG_WEBSOCKET | 3, "WebSocket: Started server thread");
while (lws_service(context, 10) >= 0);
debug(LOG_WEBSOCKET | 3, "WebSocket: shutdown voluntarily");
return NULL;
}
/* Choose mime type based on the file extension */
static char * get_mimetype(const char *resource_path)
{
char *extension = strrchr(resource_path, '.');
if (extension == NULL)
return "text/plain";
else if (!strcmp(extension, ".png"))
return "image/png";
else if (!strcmp(extension, ".svg"))
return "image/svg+xml";
else if (!strcmp(extension, ".jpg"))
return "image/jpg";
else if (!strcmp(extension, ".gif"))
return "image/gif";
else if (!strcmp(extension, ".html"))
return "text/html";
else if (!strcmp(extension, ".css"))
return "text/css";
else if (!strcmp(extension, ".js"))
return "application/javascript";
else
return "text/plain";
}
static int protocol_cb_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
{
switch (reason) {
case LWS_CALLBACK_HTTP:
if (!htdocs) {
lws_return_http_status(wsi, HTTP_STATUS_SERVICE_UNAVAILABLE, NULL);
goto try_to_reuse;
}
if (len < 1) {
lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
goto try_to_reuse;
}
char *requested_uri = (char *) in;
debug(LOG_WEBSOCKET | 3, "LWS: New HTTP request: %s", requested_uri);
/* Handle default path */
if (!strcmp(requested_uri, "/")) {
char *response = "HTTP/1.1 302 Found\r\n"
"Content-Length: 0\r\n"
"Location: /index.html\r\n"
"\r\n";
lws_write(wsi, (void *) response, strlen(response), LWS_WRITE_HTTP);
goto try_to_reuse;
}
#ifdef WITH_JANSSON
/* Return list of websocket nodes */
else if (!strcmp(requested_uri, "/nodes.json")) {
json_t *json_body = json_array();
list_foreach(struct node *n, &vt.instances) {
struct websocket *w = n->_vd;
json_t *json_node = json_pack("{ s: s, s: i, s: i, s: i, s: i, s: i }",
"name", node_name_short(n),
"id", w->id,
"connections", list_length(&w->connections),
"state", n->state,
"vectorize", n->vectorize,
"affinity", n->affinity
);
/* Add all additional fields of node here.
* This can be used for metadata */
json_object_update(json_node, config_to_json(n->cfg));
json_array_append_new(json_body, json_node);
}
char *body = json_dumps(json_body, JSON_INDENT(4));
char *header = "HTTP/1.1 200 OK\r\n"
"Connection: close\r\n"
"Content-Type: application/json\r\n"
"\r\n";
lws_write(wsi, (void *) header, strlen(header), LWS_WRITE_HTTP);
lws_write(wsi, (void *) body, strlen(body), LWS_WRITE_HTTP);
free(body);
json_decref(json_body);
return -1;
}
else if (!strcmp(requested_uri, "/config.json")) {
char *body = json_dumps(config_to_json(cfg_root), JSON_INDENT(4));
char *header = "HTTP/1.1 200 OK\r\n"
"Connection: close\r\n"
"Content-Type: application/json\r\n"
"\r\n";
lws_write(wsi, (void *) header, strlen(header), LWS_WRITE_HTTP);
lws_write(wsi, (void *) body, strlen(body), LWS_WRITE_HTTP);
free(body);
return -1;
}
#endif
else {
char path[4069];
snprintf(path, sizeof(path), "%s%s", htdocs, requested_uri);
/* refuse to serve files we don't understand */
char *mimetype = get_mimetype(path);
if (!mimetype) {
warn("HTTP: Unknown mimetype for %s", path);
lws_return_http_status(wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL);
return -1;
}
int n = lws_serve_http_file(wsi, path, mimetype, NULL, 0);
if (n < 0)
return -1;
else if (n == 0)
break;
else
goto try_to_reuse;
}
default:
return 0;
}
return 0;
try_to_reuse:
if (lws_http_transaction_completed(wsi))
return -1;
return 0;
}
static int protocol_cb_live(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
>>>>>>> feature-mpmc-queue
{
int ret;
struct connection *c = user;
struct websocket *w;
switch (reason) {
@ -39,36 +324,57 @@ int websocket_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, voi
return -1;
}
/* Search for node whose name matches the URI. */
c->node = list_lookup(&vt.instances, uri + 1);
if (c->node == NULL) {
warn("LWS: Closing Connection for non-existent node: %s", uri + 1);
return -1;
if ((uri[0] == '/' && uri[1] == 0) || uri[0] == 0){
/* Catch all connection */
c->node = NULL;
}
else {
char *node = uri + 1;
/* Check if node is running */
if (c->node->state != NODE_RUNNING)
return -1;
/* Search for node whose name matches the URI. */
c->node = list_lookup(&vt.instances, node);
if (c->node == NULL) {
warn("LWS: Closing Connection for non-existent node: %s", uri + 1);
return -1;
}
/* Check if node is running */
if (c->node->state != NODE_RUNNING)
return -1;
}
c->state = WEBSOCKET_ESTABLISHED;
c->wsi = wsi;
ret = queue_init(&c->queue, DEFAULT_QUEUELEN, &memtype_hugepage);
if (ret) {
warn("Failed to create queue for incoming websocket connection. Closing..");
return -1;
}
/* Lookup peer address for debug output */
lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), c->peer.name, sizeof(c->peer.name), c->peer.ip, sizeof(c->peer.ip));
info("LWS: New Connection for node %s from %s (%s)", node_name(c->node), c->peer.name, c->peer.ip);
info("LWS: New connection %s", connection_name(c));
struct websocket *w = (struct websocket *) c->node->_vd;
list_push(&w->connections, c);
if (c->node != NULL) {
struct websocket *w = (struct websocket *) c->node->_vd;
list_push(&w->connections, c);
}
else {
list_push(&connections, c);
}
return 0;
}
case LWS_CALLBACK_CLOSED:
info("LWS: Connection closed for node %s from %s (%s)", node_name(c->node), c->peer.name, c->peer.ip);
info("LWS: Connection %s closed", connection_name(c));
c->state = WEBSOCKET_CLOSED;
c->wsi = NULL;
queue_destroy(&c->queue);
return 0;
@ -76,36 +382,30 @@ int websocket_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, voi
case LWS_CALLBACK_SERVER_WRITEABLE: {
w = (struct websocket *) c->node->_vd;
if (c->node->state != NODE_RUNNING)
if (c->node && c->node->state != NODE_RUNNING)
return -1;
if (w->shutdown) {
lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY, (unsigned char *) "Bye", 4);
if (c->state == WEBSOCKET_SHUTDOWN) {
lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY, (unsigned char *) "Node stopped", 4);
return -1;
}
int cnt, sent, ret;
unsigned char *bufs[DEFAULT_QUEUELEN];
cnt = queue_get_many(&w->queue_tx, (void **) bufs, DEFAULT_QUEUELEN, c->sent);
for (sent = 0; sent < cnt; sent++) {
struct msg *msg = (struct msg *) (bufs[sent] + LWS_PRE);
char *buf;
int cnt;
while ((cnt = queue_pull(&c->queue, (void **) &buf))) {
struct webmsg *msg = (struct webmsg *) (buf + LWS_PRE);
ret = lws_write(wsi, (unsigned char *) msg, MSG_LEN(msg->length), LWS_WRITE_BINARY);
if (ret < MSG_LEN(msg->length))
pool_put(&w->pool, (void *) buf);
ret = lws_write(wsi, (unsigned char *) msg, WEBMSG_LEN(msg->length), LWS_WRITE_BINARY);
if (ret < WEBMSG_LEN(msg->length))
error("Failed lws_write()");
if (lws_send_pipe_choked(wsi))
break;
break;
}
queue_pull_many(&w->queue_tx, (void **) bufs, sent, &c->sent);
pool_put_many(&w->pool, (void **) bufs, sent);
if (sent < cnt)
if (queue_available(&c->queue) > 0)
lws_callback_on_writable(wsi);
return 0;
@ -118,27 +418,30 @@ int websocket_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, voi
if (c->node->state != NODE_RUNNING)
return -1;
if (!lws_frame_is_binary(wsi) || len < MSG_LEN(0))
warn("LWS: Received invalid packet for node: %s", node_name(c->node));
if (!lws_frame_is_binary(wsi) || len < WEBMSG_LEN(0))
warn("LWS: Received invalid packet for connection %s", connection_name(c));
struct msg *msg = (struct msg *) in;
struct webmsg *msg = (struct webmsg *) in;
while ((char *) msg + MSG_LEN(msg->length) <= (char *) in + len) {
struct msg *msg2 = pool_get(&w->pool);
while ((char *) msg + WEBMSG_LEN(msg->length) <= (char *) in + len) {
struct webmsg *msg2 = pool_get(&w->pool);
if (!msg2) {
warn("Pool underrun for node: %s", node_name(c->node));
return -1;
warn("Pool underrun for connection %s", connection_name(c));
break;
}
memcpy(msg2, msg, MSG_LEN(msg->length));
memcpy(msg2, msg, WEBMSG_LEN(msg->length));
queue_push(&w->queue_rx, msg2, &c->received);
ret = queue_push(&w->queue, msg2);
if (ret != 1) {
warn("Queue overrun for connection %s", connection_name(c));
break;
}
/* Next message */
msg = (struct msg *) ((char *) msg + MSG_LEN(msg->length));
msg = (struct webmsg *) ((char *) msg + WEBMSG_LEN(msg->length));
}
/** @todo Implement */
return 0;
}
@ -149,29 +452,24 @@ int websocket_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, voi
int websocket_open(struct node *n)
{
struct websocket *w = n->_vd;
int ret;
struct websocket *w = n->_vd;
w->id = id++;
list_init(&w->connections);
list_init(&w->destinations);
size_t blocklen = LWS_PRE + MSG_LEN(DEFAULT_VALUES);
size_t blocklen = LWS_PRE + WEBMSG_LEN(DEFAULT_VALUES);
ret = pool_init_mmap(&w->pool, blocklen, 2 * DEFAULT_QUEUELEN);
ret = pool_init(&w->pool, 64 * DEFAULT_QUEUELEN, blocklen, &memtype_hugepage);
if (ret)
return ret;
ret = queue_init(&w->queue_tx, DEFAULT_QUEUELEN);
ret = queue_init(&w->queue, DEFAULT_QUEUELEN, &memtype_hugepage);
if (ret)
return ret;
ret = queue_init(&w->queue_rx, DEFAULT_QUEUELEN);
if (ret)
return ret;
queue_reader_add(&w->queue_rx, 0, 0);
return 0;
}
@ -179,13 +477,13 @@ int websocket_close(struct node *n)
{
struct websocket *w = n->_vd;
w->shutdown = 1;
list_foreach(struct lws *wsi, &w->connections)
lws_callback_on_writable(wsi);
list_foreach(struct connection *c, &w->connections) {
c->state = WEBSOCKET_SHUTDOWN;
lws_callback_on_writable(c->wsi);
}
pool_destroy(&w->pool);
queue_destroy(&w->queue_tx);
queue_destroy(&w->queue);
list_destroy(&w->connections, NULL, false);
@ -194,7 +492,9 @@ int websocket_close(struct node *n)
int websocket_destroy(struct node *n)
{
// struct websocket *w = n->_vd;
struct websocket *w = n->_vd;
list_destroy(&w->destinations, (dtor_cb_t) destination_destroy, true);
return 0;
}
@ -203,17 +503,21 @@ int websocket_read(struct node *n, struct sample *smps[], unsigned cnt)
{
struct websocket *w = n->_vd;
struct msg *msgs[cnt];
struct webmsg *msgs[cnt];
int got;
got = queue_pull_many(&w->queue_rx, (void **) msgs, cnt, &w->received);
do {
got = queue_pull_many(&w->queue, (void **) msgs, cnt);
pthread_yield();
} while (got == 0);
for (int i = 0; i < got; i++) {
smps[i]->sequence = msgs[i]->sequence;
smps[i]->length = msgs[i]->length;
smps[i]->ts.origin = MSG_TS(msgs[i]);
smps[i]->ts.origin = WEBMSG_TS(msgs[i]);
memcpy(&smps[i]->data, &msgs[i]->data, MSG_DATA_LEN(msgs[i]->length));
memcpy(&smps[i]->data, &msgs[i]->data, WEBMSG_DATA_LEN(msgs[i]->length));
}
pool_put_many(&w->pool, (void **) msgs, got);
@ -225,55 +529,76 @@ int websocket_write(struct node *n, struct sample *smps[], unsigned cnt)
{
struct websocket *w = n->_vd;
int blocks, enqueued;
char *bufs[cnt];
/* Copy samples to websocket queue */
blocks = pool_get_many(&w->pool, (void **) bufs, cnt);
if (blocks != cnt)
warn("Pool underrun in websocket node: %s", node_name(n));
for (int i = 0; i < blocks; i++) {
struct msg *msg = (struct msg *) (bufs[i] + LWS_PRE);
msg->version = MSG_VERSION;
msg->type = MSG_TYPE_DATA;
msg->endian = MSG_ENDIAN_HOST;
msg->length = smps[i]->length;
msg->sequence = smps[i]->sequence;
msg->ts.sec = smps[i]->ts.origin.tv_sec;
msg->ts.nsec = smps[i]->ts.origin.tv_nsec;
memcpy(&msg->data, &smps[i]->data, smps[i]->length * 4);
list_foreach(struct connection *c, &w->connections) {
connection_write(c, smps, cnt);
}
enqueued = queue_push_many(&w->queue_tx, (void **) bufs, cnt, &w->sent);
if (enqueued != blocks)
warn("Queue overrun in websocket node: %s", node_name(n));
/* Notify all active websocket connections to send new data */
list_foreach(struct websocket_connection *c, &w->connections) {
switch (c->state) {
case WEBSOCKET_CLOSED:
queue_reader_remove(&w->queue_tx, c->sent, w->sent);
list_remove(&w->connections, c);
break;
case WEBSOCKET_ESTABLISHED:
c->sent = w->sent;
c->state = WEBSOCKET_ACTIVE;
queue_reader_add(&w->queue_tx, c->sent, w->sent);
case WEBSOCKET_ACTIVE:
lws_callback_on_writable(c->wsi);
break;
}
list_foreach(struct connection *c, &connections) {
connection_write(c, smps, cnt);
}
return cnt;
}
int websocket_parse(struct node *n, config_setting_t *cfg)
{
struct websocket *w = n->_vd;
config_setting_t *cfg_dests;
int ret;
cfg_dests = config_setting_get_member(cfg, "destinations");
if (!config_setting_is_array(cfg_dests))
cerror(cfg_dests, "The 'destinations' setting must be an array of URLs");
for (int i = 0; i < config_setting_length(cfg_dests); i++) {
struct destination *d;
const char *uri, *prot, *ads, *path;
uri = config_setting_get_string_elem(cfg_dests, i);
if (!uri)
cerror(cfg_dests, "The 'destinations' setting must be an array of URLs");
d = alloc(sizeof(struct destination));
d->uri = strdup(uri);
if (!d->uri)
serror("Failed to allocate memory");
ret = lws_parse_uri(d->uri, &prot, &ads, &d->info.port, &path);
if (ret)
cerror(cfg_dests, "Failed to parse websocket URI: '%s'", uri);
d->info.ssl_connection = !strcmp(prot, "https");
d->info.address = ads;
d->info.path = path;
d->info.protocol = prot;
d->info.ietf_version_or_minus_one = -1;
list_push(&w->destinations, d);
}
return 0;
}
char * websocket_print(struct node *n)
{
struct websocket *w = n->_vd;
char *buf = NULL;
list_foreach(struct lws_client_connect_info *in, &w->destinations) {
buf = strcatf(&buf, "%s://%s:%d/%s",
in->ssl_connection ? "https" : "http",
in->address,
in->port,
in->path
);
}
return buf;
}
static struct plugin p = {
.name = "websocket",
.description = "Send and receive samples of a WebSocket connection (libwebsockets)",
@ -286,6 +611,10 @@ static struct plugin p = {
.destroy = websocket_destroy,
.read = websocket_read,
.write = websocket_write,
.init = websocket_init,
.deinit = websocket_deinit,
.print = websocket_print,
.parse = websocket_parse
}
};

View file

@ -9,6 +9,7 @@
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <libconfig.h>
#include "config.h"
#include "utils.h"
@ -16,107 +17,109 @@
#include "timing.h"
#include "pool.h"
#include "queue.h"
#include "hook.h"
#include "plugin.h"
#include "cfg.h"
static void path_write(struct path *p, bool resend)
static void path_read(struct path *p)
{
list_foreach(struct node *n, &p->destinations) {
int cnt = n->vectorize;
int sent, tosend, available, released;
struct sample *smps[n->vectorize];
int recv;
int enqueue;
int enqueued;
int ready = 0; /**< Number of blocks in smps[] which are allocated and ready to be used by node_read(). */
struct path_source *ps = p->source;
int cnt = ps->node->vectorize;
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);
if (available == 0)
continue;
tosend = hook_run(p, smps, available, HOOK_WRITE);
if (tosend == 0)
continue;
sent = node_write(n, smps, tosend);
if (sent < 0)
error("Failed to sent %u samples to node %s", cnt, node_name(n));
else if (sent < tosend)
warn("Partial write to node %s", node_name(n));
struct sample *smps[cnt];
debug(LOG_PATH | 15, "Sent %u messages to node %s", sent, node_name(n));
/* Fill smps[] free sample blocks from the pool */
ready += sample_alloc(&ps->pool, smps, cnt - ready);
if (ready != cnt)
warn("Pool underrun for path %s", path_name(p));
released = pool_put_many(&p->pool, (void **) smps, sent);
if (sent != released)
warn("Failed to release %u samples to pool for path %s", sent - released, path_name(p));
/* Read ready samples and store them to blocks pointed by smps[] */
recv = node_read(ps->node, smps, ready);
if (recv < 0)
error("Failed to receive message from node %s", node_name(ps->node));
else if (recv < ready)
warn("Partial read for path %s: read=%u expected=%u", path_name(p), recv, ready);
debug(LOG_PATH | 15, "Received %u messages from node %s", recv, node_name(ps->node));
/* Run preprocessing hooks for vector of samples */
enqueue = hook_run(p, smps, recv, HOOK_READ);
if (enqueue != recv) {
info("Hooks skipped %u out of %u samples for path %s", recv - enqueue, recv, path_name(p));
stats_update(p->stats->delta, STATS_SKIPPED, recv - enqueue);
}
list_foreach(struct path_destination *pd, &p->destinations) {
enqueued = queue_push_many(&pd->queue, (void **) smps, enqueue);
if (enqueue != enqueued)
warn("Queue overrun for path %s", path_name(p));
for (int i = 0; i < enqueued; i++)
sample_get(smps[i]); /* increase reference count */
debug(LOG_PATH | 15, "Enqueued %u samples from %s to queue of %s", enqueued, node_name(ps->node), node_name(pd->node));
}
}
/** Send messages asynchronously */
static void * path_run_async(void *arg)
static void path_write(struct path *p)
{
struct path *p = arg;
list_foreach(struct path_destination *pd, &p->destinations) {
int cnt = pd->node->vectorize;
int sent;
int tosend;
int available;
int released;
/* Block until 1/p->rate seconds elapsed */
for (;;) {
/* Check for overruns */
uint64_t expir = timerfd_wait(p->tfd);
if (expir == 0)
perror("Failed to wait for timer");
else if (expir > 1) {
p->overrun += expir;
warn("Overrun detected for path: overruns=%" PRIu64, expir);
struct sample *smps[cnt];
/* As long as there are still samples in the queue */
while (1) {
available = queue_pull_many(&pd->queue, (void **) smps, cnt);
if (available == 0)
break;
else if (available < cnt)
debug(LOG_PATH | 5, "Queue underrun for path %s: available=%u expected=%u", path_name(p), available, cnt);
debug(LOG_PATH | 15, "Dequeued %u samples from queue of node %s which is part of path %s", available, node_name(pd->node), path_name(p));
tosend = hook_run(p, smps, available, HOOK_WRITE);
if (tosend == 0)
continue;
sent = node_write(pd->node, smps, tosend);
if (sent < 0)
error("Failed to sent %u samples to node %s", cnt, node_name(pd->node));
else if (sent < tosend)
warn("Partial write to node %s", node_name(pd->node));
debug(LOG_PATH | 15, "Sent %u messages to node %s", sent, node_name(pd->node));
released = 0;
for (int i = 0; i < sent; i++) {
if (sample_put(smps[i]) == 0)
released++; /* we had the last reference (0 remaining) */
}
debug(LOG_PATH | 15, "Released %d samples back to memory pool", released);
}
if (hook_run(p, NULL, 0, HOOK_ASYNC))
continue;
path_write(p, true);
}
return NULL;
}
/** Receive messages */
/** Main thread function per path: receive -> sent messages */
static void * path_run(void *arg)
{
struct path *p = arg;
unsigned cnt = p->in->vectorize;
int recv, enqueue, enqueued;
int ready = 0; /**< Number of blocks in smps[] which are allocated and ready to be used by node_read(). */
struct sample *smps[cnt];
/* Main thread loop */
for (;;) {
/* Fill smps[] free sample blocks from the pool */
ready += sample_get_many(&p->pool, smps, cnt - ready);
if (ready != cnt)
warn("Pool underrun for path %s", path_name(p));
/* Read ready samples and store them to blocks pointed by smps[] */
recv = p->in->_vt->read(p->in, smps, ready);
if (recv < 0)
error("Failed to receive message from node %s", node_name(p->in));
else if (recv < ready)
warn("Partial read for path %s: read=%u expected=%u", path_name(p), recv, ready);
debug(LOG_PATH | 15, "Received %u messages from node %s", recv, node_name(p->in));
/* Run preprocessing hooks for vector of samples */
enqueue = hook_run(p, smps, recv, HOOK_READ);
if (enqueue != recv) {
info("Hooks skipped %u out of %u samples for path %s", recv - enqueue, recv, path_name(p));
p->skipped += recv - 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));
ready -= enqueued;
debug(LOG_PATH | 3, "Enqueuing %u samples to queue of path %s", enqueue, path_name(p));
/* At fixed rate mode, messages are send by another (asynchronous) thread */
if (p->rate == 0)
path_write(p, false);
path_read(p);
path_write(p);
}
return NULL;
@ -126,86 +129,89 @@ int path_start(struct path *p)
{
int ret;
info("Starting path: %s (#hooks=%zu, rate=%.1f)",
path_name(p), list_length(&p->hooks), p->rate);
info("Starting path: %s (#hooks=%zu)",
path_name(p), list_length(&p->hooks));
ret = hook_run(p, NULL, 0, HOOK_PATH_START);
if (ret)
return -1;
/* At fixed rate mode, we start another thread for sending */
if (p->rate) {
p->tfd = timerfd_create_rate(p->rate);
if (p->tfd < 0)
serror("Failed to create timer");
pthread_create(&p->sent_tid, NULL, &path_run_async, p);
}
p->state = PATH_RUNNING;
return pthread_create(&p->recv_tid, NULL, &path_run, p);
return pthread_create(&p->tid, NULL, &path_run, p);
}
int path_stop(struct path *p)
{
int ret;
info("Stopping path: %s", path_name(p));
pthread_cancel(p->recv_tid);
pthread_join(p->recv_tid, NULL);
pthread_cancel(p->tid);
pthread_join(p->tid, NULL);
if (p->rate) {
pthread_cancel(p->sent_tid);
pthread_join(p->sent_tid, NULL);
close(p->tfd);
}
p->state = PATH_STOPPED;
if (hook_run(p, NULL, 0, HOOK_PATH_STOP))
ret = hook_run(p, NULL, 0, HOOK_PATH_STOP);
if (ret)
return -1;
p->state = PATH_STOPPED;
return 0;
}
const char * path_name(struct path *p)
{
if (!p->_name) {
strcatf(&p->_name, "%s " MAG("=>"), node_name_short(p->in));
list_foreach(struct node *n, &p->destinations)
strcatf(&p->_name, " %s", node_name_short(n));
if (!p->_name) {
if (list_length(&p->destinations) == 1) {
struct path_destination *pd = (struct path_destination *) list_first(&p->destinations);
strcatf(&p->_name, "%s " MAG("=>") " %s",
node_name_short(p->source->node),
node_name_short(pd->node));
}
else {
strcatf(&p->_name, "%s " MAG("=>") " [", node_name_short(p->source->node));
list_foreach(struct path_destination *pd, &p->destinations)
strcatf(&p->_name, " %s", node_name_short(pd->node));
strcatf(&p->_name, " ]");
}
}
return p->_name;
}
void path_init(struct path *p)
struct path * path_create()
{
list_init(&p->destinations);
list_init(&p->hooks);
/* Initialize hook system */
list_foreach(struct hook *h, &hooks) {
if (h->type & HOOK_INTERNAL)
list_push(&p->hooks, memdup(h, sizeof(*h)));
}
return (struct path *) alloc(sizeof(struct path));
}
p->state = PATH_CREATED;
static int path_source_destroy(struct path_source *ps)
{
pool_destroy(&ps->pool);
return 0;
}
static int path_destination_destroy(struct path_destination *pd)
{
queue_destroy(&pd->queue);
return 0;
}
int path_destroy(struct path *p)
{
hook_run(p, NULL, 0, HOOK_DEINIT); /* Release memory */
list_destroy(&p->hooks, (dtor_cb_t) hook_destroy, true);
list_destroy(&p->destinations, (dtor_cb_t) path_destination_destroy, true);
list_destroy(&p->destinations, NULL, false);
list_destroy(&p->hooks, NULL, true);
queue_destroy(&p->queue);
pool_destroy(&p->pool);
path_source_destroy(p->source);
free(p->_name);
free(p->source);
p->state = PATH_DESTROYED;
return 0;
}
@ -217,41 +223,186 @@ int path_check(struct path *p)
error("Destiation node '%s' is not supported as a sink for path '%s'", node_name(n), path_name(p));
}
if (!p->in->_vt->read)
error("Source node '%s' is not supported as source for path '%s'", node_name(p->in), path_name(p));
if (!p->source->node->_vt->read)
error("Source node '%s' is not supported as source for path '%s'", node_name(p->source->node), path_name(p));
return 0;
}
int path_prepare(struct path *p)
int path_init(struct path *p, struct cfg *cfg)
{
int ret;
int ret, max_queuelen = 0;
/* Add internal hooks if they are not already in the list*/
list_foreach(struct plugin *pl, &plugins) {
if (pl->type == PLUGIN_TYPE_HOOK) {
struct hook *h = &pl->hook;
if ((h->type & HOOK_AUTO) && /* should this hook be added implicitely? */
(list_lookup(&p->hooks, h->name) == NULL)) /* is not already in list? */
list_push(&p->hooks, memdup(h, sizeof(struct hook)));
}
}
/* We sort the hooks according to their priority before starting the path */
list_sort(&p->hooks, hooks_sort_priority);
/* Allocate hook private memory */
ret = hook_run(p, NULL, 0, HOOK_INIT);
if (ret)
error("Failed to initialize hooks of path: %s", path_name(p));
list_sort(&p->hooks, hook_cmp_priority);
list_foreach(struct hook *h, &p->hooks)
hook_init(h, cfg);
/* Parse hook arguments */
ret = hook_run(p, NULL, 0, HOOK_PARSE);
if (ret)
error("Failed to parse arguments for hooks of path: %s", path_name(p));
/* Initialize queue */
ret = pool_init(&p->pool, SAMPLE_LEN(p->samplelen), p->queuelen, &memtype_hugepage);
/* Initialize destinations */
list_foreach(struct path_destination *pd, &p->destinations) {
ret = queue_init(&pd->queue, pd->queuelen, &memtype_hugepage);
if (ret)
error("Failed to initialize queue for path");
if (pd->queuelen > max_queuelen)
max_queuelen = pd->queuelen;
}
/* Initialize source */
ret = pool_init(&p->source->pool, max_queuelen, SAMPLE_LEN(p->source->samplelen), &memtype_hugepage);
if (ret)
error("Failed to allocate memory pool for path");
ret = queue_init(&p->queue, p->queuelen, &memtype_hugepage);
if (ret)
error("Failed to initialize queue for path");
p->state = PATH_INITIALIZED;
return 0;
}
int path_uses_node(struct path *p, struct node *n) {
return (p->in == n) || list_contains(&p->destinations, n) ? 0 : 1;
list_foreach(struct path_destination *pd, &p->destinations) {
if (pd->node == n)
return 0;
}
return p->source->node == n ? 0 : -1;
}
int path_reverse(struct path *p, struct path *r)
{
int ret;
if (list_length(&p->destinations) > 1)
return -1;
struct path_destination *first_pd = list_first(&p->destinations);
list_init(&r->destinations);
list_init(&r->hooks);
/* General */
r->enabled = p->enabled;
r->cfg = p->cfg;
struct path_destination *pd = alloc(sizeof(struct path_destination));
pd->node = p->source->node;
pd->queuelen = first_pd->queuelen;
list_push(&r->destinations, pd);
struct path_source *ps = alloc(sizeof(struct path_source));
ps->node = first_pd->node;
ps->samplelen = p->source->samplelen;
r->source = ps;
list_foreach(struct hook *h, &p->hooks) {
struct hook *hc = alloc(sizeof(struct hook));
ret = hook_copy(h, hc);
if (ret)
return ret;
list_push(&r->hooks, hc);
}
return 0;
}
int path_parse(struct path *p, config_setting_t *cfg, struct list *nodes)
{
config_setting_t *cfg_out, *cfg_hook;
const char *in;
int ret, samplelen, queuelen;
struct node *source;
struct list destinations;
/* Input node */
if (!config_setting_lookup_string(cfg, "in", &in) &&
!config_setting_lookup_string(cfg, "from", &in) &&
!config_setting_lookup_string(cfg, "src", &in) &&
!config_setting_lookup_string(cfg, "source", &in))
cerror(cfg, "Missing input node for path");
source = list_lookup(nodes, in);
if (!source)
cerror(cfg, "Invalid input node '%s'", in);
/* Output node(s) */
if (!(cfg_out = config_setting_get_member(cfg, "out")) &&
!(cfg_out = config_setting_get_member(cfg, "to")) &&
!(cfg_out = config_setting_get_member(cfg, "dst")) &&
!(cfg_out = config_setting_get_member(cfg, "dest")) &&
!(cfg_out = config_setting_get_member(cfg, "sink")))
cerror(cfg, "Missing output nodes for path");
list_init(&destinations);
ret = node_parse_list(&destinations, cfg_out, nodes);
if (ret <= 0)
cerror(cfg_out, "Invalid output nodes");
/* Optional settings */
list_init(&p->hooks);
cfg_hook = config_setting_get_member(cfg, "hook");
if (cfg_hook)
hook_parse_list(&p->hooks, cfg_hook);
if (!config_setting_lookup_bool(cfg, "reverse", &p->reverse))
p->reverse = 0;
if (!config_setting_lookup_bool(cfg, "enabled", &p->enabled))
p->enabled = 1;
if (!config_setting_lookup_int(cfg, "values", &samplelen))
samplelen = DEFAULT_VALUES;
if (!config_setting_lookup_int(cfg, "queuelen", &queuelen))
queuelen = DEFAULT_QUEUELEN;
if (!IS_POW2(queuelen)) {
queuelen = LOG2_CEIL(queuelen);
warn("Queue length should always be a power of 2. Adjusting to %d", queuelen);
}
p->cfg = cfg;
/* Check if nodes are suitable */
if (source->_vt->read == NULL)
cerror(cfg, "Input node '%s' is not supported as a source.", node_name(source));
p->source = alloc(sizeof(struct path_source));
p->source->node = source;
p->source->samplelen = samplelen;
list_foreach(struct node *n, &destinations) {
if (n->_vt->write == NULL)
cerror(cfg_out, "Output node '%s' is not supported as a destination.", node_name(n));
struct path_destination *pd = alloc(sizeof(struct path_destination));
pd->node = n;
pd->queuelen = queuelen;
list_push(&p->destinations, pd);
}
list_destroy(&destinations, NULL, false);
return 0;
}

View file

@ -16,7 +16,7 @@ int pool_init(struct pool *p, size_t cnt, size_t blocksz, const struct memtype *
/* 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);
p->blocksz = p->alignment * CEIL(blocksz, p->alignment);
p->len = cnt * p->blocksz;
p->mem = m;
@ -26,7 +26,7 @@ int pool_init(struct pool *p, size_t cnt, size_t blocksz, const struct memtype *
else
debug(LOG_POOL | 4, "Allocated %#zx bytes for memory pool", p->len);
ret = queue_init(&p->queue, cnt, m);
ret = queue_init(&p->queue, LOG2_CEIL(cnt), m);
if (ret)
return ret;

View file

@ -10,19 +10,27 @@
#include "sample.h"
#include "timing.h"
int sample_get_many(struct pool *p, struct sample *smps[], int cnt) {
int sample_alloc(struct pool *p, struct sample *smps[], int cnt) {
int ret;
ret = pool_get_many(p, (void **) smps, cnt);
if (ret < 0)
return ret;
for (int i = 0; i < ret; i++)
for (int i = 0; i < ret; i++) {
smps[i]->capacity = (p->blocksz - sizeof(**smps)) / sizeof(smps[0]->data[0]);
smps[i]->pool = p;
}
return ret;
}
void sample_free(struct sample *smps[], int cnt)
{
for (int i = 0; i < cnt; i++)
pool_put(smps[i]->pool, smps[i]);
}
int sample_get(struct sample *s)
{
return atomic_fetch_add(&s->refcnt, 1) + 1;

171
lib/stats.c Normal file
View file

@ -0,0 +1,171 @@
/** Statistic collection.
*
* @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 "stats.h"
#include "hist.h"
#include "timing.h"
#include "path.h"
#include "sample.h"
#include "log.h"
static struct stats_desc {
const char *name;
const char *unit;
const char *desc;
struct {
double min;
double max;
double resolution;
} hist;
} stats_table[] = {
{ "skipped", "", "skipped messages by hooks", {0, 0, -1, }},
{ "dropped", "", "dropped messages because of reordering", {0, 0, -1, }},
{ "gap_sequence", "", "sequence number displacement of received messages", {-10, 10, 20, }},
{ "gap_sample", "", "inter message timestamps (as sent by remote)", {90e-3, 110e-3, 1e-3, }},
{ "gap_received", "", "Histogram for inter message arrival time (as seen by this instance)", {90e-3, 110e-3, 1e-3, }},
{ "owd", "s", "Histogram for one-way-delay (OWD) of received messages", {0, 1, 100e-3, }}
};
int stats_init(struct stats *s)
{
for (int i = 0; i < STATS_COUNT; i++) {
struct stats_desc *desc = &stats_table[i];
hist_create(&s->histograms[i], desc->hist.min, desc->hist.max, desc->hist.resolution);
}
s->delta = alloc(sizeof(struct stats_delta));
return 0;
}
void stats_destroy(struct stats *s)
{
for (int i = 0; i < STATS_COUNT; i++) {
hist_destroy(&s->histograms[i]);
}
free(s->delta);
}
void stats_update(struct stats_delta *d, enum stats_id id, double val)
{
assert(id >= 0 && id < STATS_COUNT);
d->values[id] = val;
d->update |= 1 << id;
}
int stats_commit(struct stats *s, struct stats_delta *d)
{
for (int i = 0; i < STATS_COUNT; i++) {
if (d->update & 1 << i)
hist_put(&s->histograms[i], d->values[i]);
}
return 0;
}
void stats_collect(struct stats_delta *s, struct sample *smps[], size_t cnt)
{
struct sample *previous = s->last;
for (int i = 0; i < cnt; i++) {
if (previous) {
stats_update(s, STATS_GAP_RECEIVED, time_delta(&previous->ts.received, &smps[i]->ts.received));
stats_update(s, STATS_GAP_SAMPLE, time_delta(&previous->ts.origin, &smps[i]->ts.origin));
stats_update(s, STATS_OWD, time_delta(&smps[i]->ts.origin, &smps[i]->ts.received));
stats_update(s, STATS_GAP_SEQUENCE, smps[i]->sequence - (int32_t) previous->sequence);
}
previous = smps[i];
}
if (s->last)
sample_put(s->last);
if (previous)
sample_get(previous);
s->last = previous;
}
#ifdef WITH_JANSSON
json_t * stats_json(struct stats *s)
{
json_t *obj = json_object();
for (int i = 0; i < STATS_COUNT; i++) {
struct stats_desc *desc = &stats_table[i];
json_t *stats = hist_json(&s->histograms[i]);
json_object_set(obj, desc->name, stats);
}
return obj;
}
#endif
void stats_reset(struct stats *s)
{
for (int i = 0; i < STATS_COUNT; i++) {
hist_reset(&s->histograms[i]);
}
}
void stats_print_header()
{
#define UNIT(u) "(" YEL(u) ")"
stats("%-40s|%19s|%19s|%19s|%19s|", "Source " MAG("=>") " Destination",
"OWD" UNIT("S") " ",
"Rate" UNIT("p/S") " ",
"Drop" UNIT("p") " ",
"Skip" UNIT("p") " "
);
line();
}
void stats_print_periodic(struct stats *s, struct path *p)
{
stats("%-40.40s|%10f|%10f|%10ju|%10ju|", path_name(p),
s->histograms[STATS_OWD].last,
1.0 / s->histograms[STATS_GAP_SAMPLE].last,
s->histograms[STATS_DROPPED].total,
s->histograms[STATS_SKIPPED].total
);
}
void stats_print(struct stats *s, int details)
{
for (int i = 0; i < STATS_COUNT; i++) {
struct stats_desc *desc = &stats_table[i];
stats("%s: %s", desc->name, desc->desc);
hist_print(&s->histograms[i], details);
}
}
void stats_send(struct stats *s, struct node *n)
{
char buf[SAMPLE_LEN(STATS_COUNT * 5)];
struct sample *smp = (struct sample *) buf;
int i = 0;
for (int j = 0; j < STATS_COUNT; j++) {
smp->data[i++].f = s->histograms[j].last;
smp->data[i++].f = s->histograms[j].highest;
smp->data[i++].f = s->histograms[j].lowest;
smp->data[i++].f = hist_mean(&s->histograms[j]);
smp->data[i++].f = hist_var(&s->histograms[j]);
}
smp->length = i;
node_write(n, &smp, 1); /* Send single message with statistics to destination node */
}

View file

@ -324,43 +324,6 @@ ssize_t read_random(char *buf, size_t len)
return bytes;
}
void printb(void *mem, size_t len)
{
uint8_t *mem8 = (uint8_t *) mem;
for (int i = 0; i < len; i++) {
printf("%02hx ", mem8[i]);
if (i % 16 == 15)
printf("\n");
}
}
void printdw(void *mem, size_t len)
{
int columns = 4;
uint32_t *mem32 = (uint32_t *) mem;
for (int i = 0; i < len; i++) {
if (i % columns == 0)
printf("%#x: ", i * 4);
printf("%08x ", mem32[i]);
char *memc = (char *) &mem32[i];
printf("%c%c%c%c ",
isprint(memc[0]) ? memc[0] : ' ',
isprint(memc[1]) ? memc[1] : ' ',
isprint(memc[2]) ? memc[2] : ' ',
isprint(memc[3]) ? memc[3] : ' '
);
if ((i+1) % columns == 0)
printf("\n");
}
}
void rdtsc_sleep(uint64_t nanosecs, uint64_t start)
{
uint64_t cycles;

View file

@ -9,17 +9,16 @@
#include <linux/limits.h>
#if 0
#include "nodes/websocket.h"
#endif
#include "utils.h"
#include "log.h"
#include "web.h"
#include "api.h"
#include "nodes/websocket.h"
/* Forward declarations */
lws_callback_function api_protocol_cb;
lws_callback_function websocket_protocol_cb;
/** Path to the directory which should be served by build in HTTP server */
static char htdocs[PATH_MAX] = "/usr/local/share/villas/node/htdocs";
@ -46,7 +45,7 @@ static struct lws_protocols protocols[] = {
.per_session_data_size = sizeof(struct api_session),
.rx_buffer_size = 0
},
{ 0 /* terminator */ }
{ NULL /* terminator */ }
};
/** List of libwebsockets mounts. */
@ -118,23 +117,16 @@ int web_service(struct web *w)
return lws_service(w->context, 10);
}
int web_parse(struct web *w, config_setting_t *lcs)
int web_parse(struct web *w, config_setting_t *cfg)
{
config_setting_t *lcs_http;
if (!config_setting_is_group(cfg))
cerror(cfg, "Setting 'http' must be a group.");
/* Parse global config */
lcs_http = config_setting_lookup(lcs, "http");
if (lcs_http) {
const char *ht;
config_setting_lookup_string(lcs_http, "ssl_cert", &w->ssl_cert);
config_setting_lookup_string(lcs_http, "ssl_private_key", &w->ssl_private_key);
config_setting_lookup_int(lcs_http, "port", &w->port);
if (config_setting_lookup_string(lcs_http, "htdocs", &w->htdocs)) {
strncpy(htdocs, ht, sizeof(htdocs));
}
}
config_setting_lookup_string(cfg, "ssl_cert", &w->ssl_cert);
config_setting_lookup_string(cfg, "ssl_private_key", &w->ssl_private_key);
config_setting_lookup_int(cfg, "port", &w->port);
config_setting_lookup_string(cfg, "htdocs", &w->htdocs);
return 0;
}
@ -143,11 +135,14 @@ int web_init(struct web *w, struct api *a)
{
w->api = a;
/** @todo this is a hack */
strncpy(htdocs, w->htdocs, sizeof(htdocs));
lws_set_log_level((1 << LLL_COUNT) - 1, logger);
/* Start server */
struct lws_context_creation_info ctx_info = {
.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS,
.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT,
.gid = -1,
.uid = -1,
.user = (void *) w

View file

@ -5,6 +5,8 @@
*********************************************************************************/
#include <stddef.h>
#include <villas/hook.h>
#include <villas/log.h>
#include <villas/plugin.h>
@ -12,7 +14,7 @@ struct hook;
struct path;
struct sample;
static int hook_example(struct path *p, struct hook *h, int when, struct sample *smps[], size_t cnt)
static int hook_example(struct hook *h, int when, struct hook_info *j)
{
info("Hello world from example hook!");

View file

@ -2,7 +2,8 @@
TARGETS = $(BUILDDIR)/villas-node \
$(BUILDDIR)/villas-pipe \
$(BUILDDIR)/villas-signal \
$(BUILDDIR)/villas-test
$(BUILDDIR)/villas-test \
$(BUILDDIR)/villas-hook
SRC_LDLIBS = $(LDLIBS) -pthread -lm -lvillas
SRC_CFLAGS = $(CFLAGS)

View file

@ -25,13 +25,9 @@
/* Declarations */
int fpga_benchmarks(int argc, char *argv[], struct fpga_card *c);
struct cfg cfg;
void usage(char *name)
void usage()
{
printf("Usage: %s CONFIGFILE CARD CMD [OPTIONS]\n", name);
printf(" Commands:\n");
printf(" benchmarks Do benchmarks\n\n");
printf("Usage: villas-fpga CONFIGFILE CARD [OPTIONS]\n\n");
printf(" Options:\n");
printf(" -d Set log level\n\n");
@ -43,17 +39,11 @@ void usage(char *name)
int main(int argc, char *argv[])
{
int ret;
struct cfg cfg;
struct fpga_card *card;
enum {
FPGA_BENCH
} subcommand;
if (argc < 4)
usage(argv[0]);
if (strcmp(argv[2], "benchmarks") == 0)
subcommand = FPGA_BENCH;
else
if (argc < 3)
usage(argv[0]);
/* Parse arguments */
@ -66,18 +56,24 @@ int main(int argc, char *argv[])
case '?':
default:
usage(argv[0]);
usage();
}
}
info("Parsing configuration");
cfg_parse(&cfg, argv[1]);
info("Initialize logging system");
log_init(&cfg.log, cfg.log.level, cfg.log.facilities);
info("Initialize real-time system");
rt_init(&cfg);
rt_init(cfg.priority, cfg.affinity);
info("Initialize memory system");
memory_init();
/* Initialize VILLASfpga card */
ret = fpga_init(argc, argv, config_root_setting(cfg.cfg));
ret = fpga_init(argc, argv, config_root_setting(&cfg.cfg));
if (ret)
error("Failed to initialize FPGA card");
@ -87,10 +83,8 @@ int main(int argc, char *argv[])
fpga_card_dump(card);
/* Start subcommand */
switch (subcommand) {
case FPGA_BENCH: fpga_benchmarks(argc-optind-1, argv+optind+1, card); break;
}
/* Run benchmarks */
fpga_benchmarks(argc-optind-1, argv+optind+1, card);
/* Shutdown */
ret = fpga_deinit();

139
src/hook.c Normal file
View file

@ -0,0 +1,139 @@
/** Receive messages from server snd print them on stdout.
*
* @file
* @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.
*
* @addtogroup tools Test and debug tools
* @{
*********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <villas/sample.h>
#include <villas/hook.h>
#include <villas/utils.h>
#include <villas/pool.h>
#include <villas/log.h>
#include <villas/plugin.h>
#include <villas/kernel/rt.h>
#include "config.h"
static void usage()
{
printf("Usage: villas-hook [OPTIONS] NAME [PARAMETER] \n");
printf(" NAME the name of the hook function to run\n");
printf(" PARAM the name of the node to which samples are sent and received from\n\n");
printf(" OPTIONS are:\n");
printf(" -h show this help\n");
printf(" -d lvl set debug level\n");
printf(" -v process multiple samples at once\n\n");
print_copyright();
}
int main(int argc, char *argv[])
{
int j, ret, cnt = 1;
char *name, *parameter;
struct log log;
struct hook *h;
struct plugin *p;
struct hook_info *i = alloc(sizeof(struct hook_info));
char c;
while ((c = getopt(argc, argv, "hv:d:")) != -1) {
switch (c) {
case 'v':
cnt = atoi(optarg);
break;
case 'd':
log.level = atoi(optarg);
break;
case 'h':
case '?':
usage();
exit(c == '?' ? EXIT_FAILURE : EXIT_SUCCESS);
}
}
if (argc <= optind)
name = argc > optind ? argv[optind ] : NULL;
parameter = argc > optind + 1 ? argv[optind+1] : NULL;
if (argc > optind)
name = argv[optind];
else {
usage(argv[0]);
exit(EXIT_FAILURE);
}
if (argc > optind + 1)
parameter = argv[optind + 1];
p = plugin_lookup(PLUGIN_TYPE_HOOK, name);
if (!p)
error("Unknown hook function '%s'", argv[optind]);
h = &p->hook;
if (cnt < 1)
error("Vectorize option must be greater than 0");
struct pool pool;
struct sample *smps[cnt];
info("Initialize logging system");
log_init(&log, log.level, LOG_ALL);
info("Initialize real-time system");
rt_init(-1, 50);
info("Initialize memory system");
memory_init();
ret = pool_init(&pool, 10 * cnt, SAMPLE_LEN(DEFAULT_VALUES), &memtype_hugepage);
if (ret)
error("Failed to initilize memory pool");
ret = sample_alloc(&pool, smps, cnt);
if (ret)
error("Failed to allocate %u samples from pool", cnt);
h->parameter = parameter;
i->smps = smps;
h->cb(h, HOOK_INIT, i);
h->cb(h, HOOK_PARSE, i);
h->cb(h, HOOK_PATH_START, i);
while (!feof(stdin)) {
for (j = 0; j < cnt && !feof(stdin); j++)
sample_fscan(stdin, i->smps[j], NULL);
i->cnt = j;
i->cnt = h->cb(h, HOOK_READ, i);
i->cnt = h->cb(h, HOOK_WRITE, i);
for (j = 0; j < i->cnt; j++)
sample_fprint(stdout, i->smps[j], SAMPLE_ALL);
}
h->cb(h, HOOK_PATH_STOP, i);
h->cb(h, HOOK_DESTROY, i);
sample_free(smps, cnt);
pool_destroy(&pool);
return 0;
}

View file

@ -8,11 +8,10 @@
#include <signal.h>
#include <unistd.h>
#include "config.h"
#include <villas/utils.h>
#include <villas/cfg.h>
#include <villas/path.h>
#include <villas/memory.h>
#include <villas/node.h>
#include <villas/api.h>
#include <villas/web.h>
@ -20,30 +19,28 @@
#include <villas/plugin.h>
#include <villas/kernel/kernel.h>
#include <villas/kernel/rt.h>
/* Forward declarations */
void hook_stats_header();
#include <villas/hook.h>
#ifdef ENABLE_OPAL_ASYNC
#include "opal.h"
#include <villas/nodes/opal.h>
#endif
struct cfg config;
struct cfg cfg;
static void quit()
{
info("Stopping paths");
list_foreach(struct path *p, &config.paths) { INDENT
list_foreach(struct path *p, &cfg.paths) { INDENT
path_stop(p);
}
info("Stopping nodes");
list_foreach(struct node *n, &config.nodes) { INDENT
list_foreach(struct node *n, &cfg.nodes) { INDENT
node_stop(n);
}
cfg_deinit(&config);
cfg_destroy(&config);
cfg_deinit(&cfg);
cfg_destroy(&cfg);
info(GRN("Goodbye!"));
@ -63,14 +60,14 @@ static void signals_init()
sigaction(SIGTERM, &sa_quit, NULL);
}
static void usage(const char *name)
static void usage()
{
printf("Usage: %s [CONFIG]\n", name);
printf("Usage: villas-node [CONFIG]\n");
printf(" CONFIG is the path to an optional configuration file\n");
printf(" if omitted, VILLASnode will start without a configuration\n");
printf(" and wait for provisioning over the web interface.\n\n");
#ifdef ENABLE_OPAL_ASYNC
printf("Usage: %s OPAL_ASYNC_SHMEM_NAME OPAL_ASYNC_SHMEM_SIZE OPAL_PRINT_SHMEM_NAME\n", name);
printf("Usage: villas-node OPAL_ASYNC_SHMEM_NAME OPAL_ASYNC_SHMEM_SIZE OPAL_PRINT_SHMEM_NAME\n");
printf(" This type of invocation is used by OPAL-RT Asynchronous processes.\n");
printf(" See in the RT-LAB User Guide for more information.\n\n");
#endif
@ -106,6 +103,8 @@ int main(int argc, char *argv[])
char *uri = (argc == 2) ? argv[1] : NULL;
#endif
log_init(&cfg.log, V, LOG_ALL);
info("This is VILLASnode %s (built on %s, %s)", BLD(YEL(VERSION)),
BLD(MAG(__DATE__)), BLD(MAG(__TIME__)));
@ -117,22 +116,22 @@ int main(int argc, char *argv[])
signals_init();
info("Parsing configuration");
cfg_init_pre(&config);
cfg_init_pre(&cfg);
cfg_parse(&config, uri);
cfg_parse(&cfg, uri);
cfg_init_post(&config);
cfg_init_post(&cfg);
info("Initialize node types");
list_foreach(struct node_type *vt, &node_types) { INDENT
int refs = list_length(&vt->instances);
if (refs > 0)
node_init(vt, argc, argv, config_root_setting(config.cfg));
node_init(vt, argc, argv, config_root_setting(&cfg.cfg));
}
info("Starting nodes");
list_foreach(struct node *n, &config.nodes) { INDENT
int refs = list_count(&config.paths, (cmp_cb_t) path_uses_node, n);
list_foreach(struct node *n, &cfg.nodes) { INDENT
int refs = list_count(&cfg.paths, (cmp_cb_t) path_uses_node, n);
if (refs > 0)
node_start(n);
else
@ -140,32 +139,32 @@ int main(int argc, char *argv[])
}
info("Starting paths");
list_foreach(struct path *p, &config.paths) { INDENT
list_foreach(struct path *p, &cfg.paths) { INDENT
if (p->enabled) {
path_prepare(p);
path_init(p, &cfg);
path_start(p);
}
else
warn("Path %s is disabled. Skipping...", path_name(p));
}
if (config.stats > 0)
hook_stats_header();
if (cfg.stats > 0)
stats_print_header();
struct timespec now, last = time_now();
/* Run! Until signal handler is invoked */
while (1) {
now = time_now();
if (config.stats > 0 && time_delta(&last, &now) > config.stats) {
list_foreach(struct path *p, &config.paths) {
if (cfg.stats > 0 && time_delta(&last, &now) > cfg.stats) {
list_foreach(struct path *p, &cfg.paths) {
hook_run(p, NULL, 0, HOOK_PERIODIC);
}
last = time_now();
}
web_service(&config.web); /** @todo Maybe we should move this to another thread */
web_service(&cfg.web); /** @todo Maybe we should move this to another thread */
}
return 0;

View file

@ -45,15 +45,14 @@ static void quit(int signal, siginfo_t *sinfo, void *ctx)
if (recvv.started) {
pthread_cancel(recvv.thread);
pthread_join(recvv.thread, NULL);
pool_destroy(&recvv.pool);
}
if (sendd.started) {
pthread_cancel(sendd.thread);
pthread_join(sendd.thread, NULL);
pool_destroy(&sendd.pool);
}
pool_destroy(&recvv.pool);
pool_destroy(&sendd.pool);
node_stop(node);
node_deinit(node->_vt);
@ -64,9 +63,9 @@ static void quit(int signal, siginfo_t *sinfo, void *ctx)
exit(EXIT_SUCCESS);
}
static void usage(char *name)
static void usage()
{
printf("Usage: %s CONFIG NODE [OPTIONS]\n", name);
printf("Usage: villas-pipe CONFIG NODE [OPTIONS]\n");
printf(" CONFIG path to a configuration file\n");
printf(" NODE the name of the node to which samples are sent and received from\n");
printf(" OPTIONS are:\n");
@ -91,11 +90,11 @@ static void * send_loop(void *ctx)
sendd.started = true;
/* Initialize memory */
ret = pool_init(&sendd.pool, node->vectorize, SAMPLE_LEN(DEFAULT_VALUES), &memtype_hugepage);
ret = pool_init(&sendd.pool, LOG2_CEIL(node->vectorize), SAMPLE_LEN(DEFAULT_VALUES), &memtype_hugepage);
if (ret < 0)
error("Failed to allocate memory for receive pool.");
ret = sample_get_many(&sendd.pool, smps, node->vectorize);
ret = sample_alloc(&sendd.pool, smps, node->vectorize);
if (ret < 0)
error("Failed to get %u samples out of send pool (%d).", node->vectorize, ret);
@ -135,11 +134,11 @@ static void * recv_loop(void *ctx)
recvv.started = true;
/* Initialize memory */
ret = pool_init(&recvv.pool, node->vectorize, SAMPLE_LEN(DEFAULT_VALUES), &memtype_hugepage);
ret = pool_init(&recvv.pool, LOG2_CEIL(node->vectorize), SAMPLE_LEN(DEFAULT_VALUES), &memtype_hugepage);
if (ret < 0)
error("Failed to allocate memory for receive pool.");
ret = sample_get_many(&recvv.pool, smps, node->vectorize);
ret = sample_alloc(&recvv.pool, smps, node->vectorize);
if (ret < 0)
error("Failed to get %u samples out of receive pool (%d).", node->vectorize, ret);
@ -171,11 +170,10 @@ int main(int argc, char *argv[])
char c;
ptid = pthread_self();
log_init(&cfg.log);
/* Parse command line arguments */
if (argc < 3)
usage(argv[0]);
usage();
/* Default values */
sendd.enabled = true;
@ -197,9 +195,12 @@ int main(int argc, char *argv[])
break;
case 'h':
case '?':
usage(argv[0]);
usage();
exit(c == '?' ? EXIT_FAILURE : EXIT_SUCCESS);
}
}
log_init(&cfg.log, cfg.log.level, LOG_ALL);
/* Setup signals */
struct sigaction sa_quit = {
@ -216,7 +217,10 @@ int main(int argc, char *argv[])
cfg_parse(&cfg, argv[1]);
info("Initialize real-time system");
rt_init(&cfg);
rt_init(cfg.priority, cfg.affinity);
info("Initialize memory system");
memory_init();
/* Initialize node */
node = list_lookup(&nodes, argv[2]);
@ -226,7 +230,7 @@ int main(int argc, char *argv[])
if (reverse)
node_reverse(node);
ret = node_init(node->_vt, argc-optind, argv+optind, config_root_setting(cfg.cfg));
ret = node_init(node->_vt, argc-optind, argv+optind, config_root_setting(&cfg.cfg));
if (ret)
error("Failed to intialize node: %s", node_name(node));

View file

@ -12,10 +12,11 @@
#include <math.h>
#include <string.h>
#include <villas/utils.h>
#include <villas/sample.h>
#include <villas/timing.h>
#include "config.h"
#include "utils.h"
#include "sample.h"
#include "timing.h"
#define CLOCKID CLOCK_REALTIME
@ -28,9 +29,9 @@ enum SIGNAL_TYPE {
TYPE_MIXED
};
void usage(char *name)
void usage()
{
printf("Usage: %s SIGNAL [OPTIONS]\n", name);
printf("Usage: villas-signal SIGNAL [OPTIONS]\n");
printf(" SIGNAL is on of: 'mixed', 'random', 'sine', 'triangle', 'square', 'ramp'\n");
printf(" -v NUM specifies how many values a message should contain\n");
printf(" -r HZ how many messages per second\n");
@ -56,10 +57,10 @@ int main(int argc, char *argv[])
int limit = -1;
int counter;
log_init(&log);
log_init(&log, V, LOG_ALL);
if (argc < 2) {
usage(argv[0]);
usage();
exit(EXIT_FAILURE);
}
@ -101,7 +102,8 @@ int main(int argc, char *argv[])
goto check;
case 'h':
case '?':
usage(argv[0]);
usage();
exit(c == '?' ? EXIT_FAILURE : EXIT_SUCCESS);
}
continue;
@ -109,7 +111,7 @@ int main(int argc, char *argv[])
check: if (optarg == endptr)
error("Failed to parse parse option argument '-%c %s'", c, optarg);
}
/* Allocate memory for message buffer */
struct sample *s = alloc(SAMPLE_LEN(values));

View file

@ -11,14 +11,15 @@
#include <ctype.h>
#include <sys/stat.h>
#include <villas/cfg.h>
#include <villas/node.h>
#include <villas/utils.h>
#include <villas/hist.h>
#include <villas/timing.h>
#include <villas/pool.h>
#include <villas/kernel/rt.h>
#include "config.h"
#include "cfg.h"
#include "msg.h"
#include "node.h"
#include "utils.h"
#include "hist.h"
#include "timing.h"
#include "pool.h"
struct cfg cfg; /** <The global configuration */
@ -50,9 +51,9 @@ void quit()
running = 0;
}
void usage(char *name)
void usage()
{
printf("Usage: %s CONFIG TEST NODE [ARGS]\n", name);
printf("Usage: villas-test CONFIG TEST NODE [ARGS]\n");
printf(" CONFIG path to a configuration file\n");
printf(" TEST the name of the test to execute: 'rtt'\n");
printf(" NODE name of the node which shoud be used\n\n");
@ -63,7 +64,7 @@ void usage(char *name)
int main(int argc, char *argv[])
{
if (argc < 4) {
usage(argv[0]);
usage();
exit(EXIT_FAILURE);
}
@ -77,14 +78,22 @@ int main(int argc, char *argv[])
sigaction(SIGTERM, &sa_quit, NULL);
sigaction(SIGINT, &sa_quit, NULL);
log_init(&cfg.log);
log_init(&cfg.log, V, LOG_ALL);
info("Parsing configuration");
cfg_parse(&cfg, argv[1]);
info("Initialize real-time system");
rt_init(cfg.priority, cfg.affinity);
info("Initialize memory system");
memory_init();
node = list_lookup(&cfg.nodes, argv[3]);
if (!node)
error("There's no node with the name '%s'", argv[3]);
node_init(node->_vt, argc-3, argv+3, config_root_setting(cfg.cfg));
node_init(node->_vt, argc-3, argv+3, config_root_setting(&cfg.cfg));
node_start(node);
/* Parse Arguments */
@ -107,15 +116,8 @@ int main(int argc, char *argv[])
res = strtod(optarg, &endptr);
goto check;
case '?':
if (optopt == 'c')
error("Option -%c requires an argument.", optopt);
else if (isprint(optopt))
error("Unknown option '-%c'.", optopt);
else
error("Unknown option character '\\x%x'.", optopt);
exit(EXIT_FAILURE);
default:
abort();
usage();
exit(c == '?' ? EXIT_FAILURE : EXIT_SUCCESS);
}
continue;
@ -176,12 +178,12 @@ void test_rtt() {
struct stat st;
if (!fstat(fd, &st)) {
FILE *f = fdopen(fd, "w");
hist_matlab(&hist, f);
hist_dump_matlab(&hist, f);
}
else
error("Invalid file descriptor: %u", fd);
hist_print(&hist);
hist_print(&hist, 1);
hist_destroy(&hist);
}

View file

@ -8,7 +8,6 @@ TEST_LDLIBS = $(LDLIBS) -lcriterion -lvillas -pthread
tests: $(BUILDDIR)/testsuite
run-tests: tests
echo 25 > /proc/sys/vm/nr_hugepages
$(BUILDDIR)/testsuite
# Compile

View file

@ -16,8 +16,10 @@ const int hist_result[] = {};
Test(hist, simple) {
struct hist h;
int ret;
hist_create(&h, -100, 100, 1);
ret = hist_create(&h, -100, 100, 1);
cr_assert_eq(ret, 0);
for (int i = 0; i < ARRAY_LEN(test_data); i++)
hist_put(&h, test_data[i]);

26
tests/main.c Normal file
View file

@ -0,0 +1,26 @@
/** Custom main() for Criterion
*
* @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 <villas/log.h>
#include <villas/memory.h>
int main(int argc, char *argv[]) {
struct criterion_test_set *tests = criterion_initialize();
info("Initialize memory system");
memory_init();
int result = 0;
if (criterion_handle_args(argc, argv, true))
result = !criterion_run_all_tests(tests);
criterion_finalize(tests);
return result;
}

View file

@ -13,8 +13,7 @@
#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, 32, 55, 1 << 10, 1 << 20),
DataPoints(size_t, 1, 8, 1 << 12),
DataPoints(const struct memtype *, &memtype_heap, &memtype_hugepage)
};
@ -26,7 +25,7 @@ Theory((size_t len, size_t align, const struct memtype *m), memory, aligned) {
ptr = memory_alloc_aligned(m, len, align);
cr_assert_neq(ptr, NULL, "Failed to allocate memory");
//cr_assert(IS_ALIGNED(ptr, align));
cr_assert(IS_ALIGNED(ptr, align));
if (m == &memtype_hugepage) {
cr_assert(IS_ALIGNED(ptr, HUGEPAGESIZE));

@ -1 +1 @@
Subproject commit 0d1170b449a2b867901c3f8b5cef7aae9a5c122d
Subproject commit e7a7e58b4dc07540eb19956a12c14599f990bc83

View file

@ -174,7 +174,7 @@ function wsConnect(url, protocol) {
connection.onmessage = function(e) {
var msgs = Msg.fromArrayBufferVector(e.data);
console.log('Received ' + msgs.length + ' messages with ' + msgs[0].data.length + ' values: ' + msgs[0].timestamp);
console.log('Received ' + msgs.length + ' messages with ' + msgs[0].data.length + ' values from id ' + msgs[0].id + ' with timestamp ' + msgs[0].timestamp);
for (var j = 0; j < plotData.length; j++) {
// remove old

View file

@ -18,6 +18,7 @@ function Msg(c, d)
this.endian = typeof c.endian === 'undefined' ? Msg.prototype.ENDIAN_LITTLE : c.endian;
this.version = typeof c.version === 'undefined' ? Msg.prototype.VERSION : c.version;
this.type = typeof c.type === 'undefined' ? Msg.prototype.TYPE_DATA : c.type;
this.id = typeof c.id === 'undefined' ? -1 : c.id;
this.timestamp = typeof c.timestamp === 'undefined' ? Date.now() : c.timestamp;
if (Array.isArray(d)) {
@ -53,6 +54,7 @@ Msg.fromArrayBuffer = function(data)
endian: (bits >> Msg.prototype.OFFSET_ENDIAN) & 0x1,
version: (bits >> Msg.prototype.OFFSET_VERSION) & 0xF,
type: (bits >> Msg.prototype.OFFSET_TYPE) & 0x3,
id: data.getUint8( 0x01, endian),
length: data.getUint16(0x02, endian),
sequence: data.getUint32(0x04, endian),
timestamp: data.getUint32(0x08, endian) * 1e3 +
@ -107,6 +109,7 @@ Msg.prototype.toArrayBuffer = function()
var nsec = (this.timestamp - sec * 1e3) * 1e6;
view.setUint8( 0x00, bits, true);
view.setUint8( 0x01, this.id, true);
view.setUint16(0x02, this.length, true);
view.setUint32(0x04, this.sequence, true);
view.setUint32(0x08, sec, true);