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:
commit
9c40931a04
73 changed files with 6516 additions and 1470 deletions
6
config.h
6
config.h
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -33,7 +33,8 @@ nodes = {
|
|||
series = (
|
||||
{ label = "Random walk" },
|
||||
{ label = "Sine" },
|
||||
{ label = "Rect" }
|
||||
{ label = "Rect" },
|
||||
{ label = "Ramp" }
|
||||
)
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
|
@ -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
|
|
@ -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);
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
||||
/** @} */
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
/** @} */
|
|
@ -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); \
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
73
include/villas/stats.h
Normal 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_ */
|
|
@ -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;
|
||||
}
|
||||
|
|
102
include/villas/webmsg_format.h
Normal file
102
include/villas/webmsg_format.h
Normal 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_ */
|
|
@ -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
135
lib/api.c
|
@ -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 =
|
||||
|
|
|
@ -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
19
lib/boolean/boolean.l
Normal 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
241
lib/boolean/boolean.output
Normal 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
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
85
lib/boolean/boolean.tab.h
Normal 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
74
lib/boolean/boolean.y
Normal 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);
|
||||
}
|
1804
lib/boolean/lex.log_expression_.c
Normal file
1804
lib/boolean/lex.log_expression_.c
Normal file
File diff suppressed because it is too large
Load diff
332
lib/cfg.c
332
lib/cfg.c
|
@ -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;
|
||||
}
|
||||
}
|
90
lib/hist.c
90
lib/hist.c
|
@ -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
158
lib/hook.c
Normal 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;
|
||||
}
|
64
lib/hooks.c
64
lib/hooks.c
|
@ -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
66
lib/hooks/convert.c
Normal 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
67
lib/hooks/decimate.c
Normal 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
74
lib/hooks/drop.c
Normal 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
54
lib/hooks/fix_ts.c
Normal 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)
|
||||
|
||||
/** @} */
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
39
lib/hooks/print.c
Normal 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
53
lib/hooks/restart.c
Normal 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
83
lib/hooks/skip_first.c
Normal 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
116
lib/hooks/stats.c
Normal 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
39
lib/hooks/ts.c
Normal 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)
|
||||
|
||||
/** @} */
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
19
lib/log.c
19
lib/log.c
|
@ -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);
|
||||
|
||||
|
|
44
lib/memory.c
44
lib/memory.c
|
@ -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;
|
||||
|
|
103
lib/node.c
103
lib/node.c
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
|
439
lib/path.c
439
lib/path.c
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
12
lib/sample.c
12
lib/sample.c
|
@ -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
171
lib/stats.c
Normal 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 */
|
||||
}
|
37
lib/utils.c
37
lib/utils.c
|
@ -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;
|
||||
|
|
35
lib/web.c
35
lib/web.c
|
@ -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
|
||||
|
|
|
@ -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!");
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
38
src/fpga.c
38
src/fpga.c
|
@ -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
139
src/hook.c
Normal 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;
|
||||
}
|
55
src/node.c
55
src/node.c
|
@ -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;
|
||||
|
|
32
src/pipe.c
32
src/pipe.c
|
@ -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));
|
||||
|
||||
|
|
20
src/signal.c
20
src/signal.c
|
@ -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));
|
||||
|
||||
|
|
48
src/test.c
48
src/test.c
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
26
tests/main.c
Normal 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;
|
||||
}
|
|
@ -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));
|
||||
|
|
2
thirdparty/libwebsockets
vendored
2
thirdparty/libwebsockets
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 0d1170b449a2b867901c3f8b5cef7aae9a5c122d
|
||||
Subproject commit e7a7e58b4dc07540eb19956a12c14599f990bc83
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue