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

merge upstream

This commit is contained in:
Steffen Vogel 2016-05-19 13:42:32 +02:00
commit affb5c6eb3
73 changed files with 2091 additions and 1364 deletions

View file

@ -6,7 +6,7 @@ This is S2SS, a gateway to forward and process simulation data between real time
The docuementation for this software is available at [documentation/Mainpage](documentation/Mainpage.md).
You can access the prebuild documentation at: http://46.101.131.212/s2ss/doc/.
You can access the prebuild documentation at: http://s2ss.0l.de (User: `s2ss`, Pass: `Nie4di5e`).
Alternatively, you can build the documentation yourself by calling `doxygen` in this directory.
## Contact

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -8,8 +8,8 @@
#ifndef _CONFIG_H_
#define _CONFIG_H_
#define PROGNAME "S2SS"
#define VERSION "0.1"
#define PROGNAME "S2SS-OPAL-UDP"
#define VERSION "0.5"
#define MAX_VALUES 64

View file

@ -12,11 +12,11 @@
#include "msg_format.h"
/** Swap a message to host byte order.
/** Swaps message contents byte-order.
*
* Message can either be transmitted in little or big endian
* format. The actual endianess for a message is defined by the
* msg::byteorder field.
* msg::endian field. This covers msg::length, msg::sequence, msg::data and msg::ts fields.
* Received message are usally converted to the endianess of the host.
* This is required for further sanity checks of the sequence number
* or parsing of the data.

View file

@ -80,18 +80,16 @@ struct msg
#endif
unsigned rsvd2 : 8; /**< Reserved bits */
/** The number of values in msg::data[] */
uint16_t length;
uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages */
uint16_t length; /**< The number of values in msg::data[]. Endianess is specified in msg::endian. */
uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages. Endianess is specified in msg::endian. */
/** A timestamp per message */
/** A timestamp per message. Endianess is specified in msg::endian. */
struct {
uint32_t sec; /**< Seconds since 1970-01-01 00:00:00 */
uint32_t nsec; /**< Nanoseconds since 1970-01-01 00:00:00 */
uint32_t nsec; /**< Nanoseconds of the current second. */
} ts;
/** The message payload */
/** The message payload. Endianess is specified in msg::endian. */
union {
float f; /**< Floating point values (note msg::endian) */
uint32_t i; /**< Integer values (note msg::endian) */

View file

@ -49,7 +49,7 @@ endif
INCLUDES = -I.
LIBPATH = -L.
CC_OPTS =
CC_OPTS = -std=c99
LD_OPTS =
OBJS = s2ss.o msg.o utils.o socket.o

View file

@ -7,18 +7,23 @@
*********************************************************************************/
#ifdef __linux__
#include <byteswap.h>
#include <byteswap.h>
#elif defined(__PPC__) /* Xilinx toolchain */
#include <xil_io.h>
#define bswap_32(x) Xil_EndianSwap32(x)
#include <xil_io.h>
#define bswap_16(x) Xil_EndianSwap16(x)
#define bswap_32(x) Xil_EndianSwap32(x)
#endif
#include "msg.h"
void msg_swap(struct msg *m)
{
int i;
for (i = 0; i < m->length; i++)
m->length = bswap_16(m->length);
m->sequence = bswap_32(m->sequence);
m->ts.sec = bswap_32(m->ts.sec);
m->ts.nsec = bswap_32(m->ts.nsec);
for (int i = 0; i < m->length; i++)
m->data[i].i = bswap_32(m->data[i].i);
m->endian ^= 1;

View file

@ -25,12 +25,13 @@
#include <time.h>
#if defined(__QNXNTO__)
# include <process.h>
# include <pthread.h>
# include <devctl.h>
# include <sys/dcmd_chr.h>
#include <process.h>
#include <pthread.h>
#include <devctl.h>
#include <sys/dcmd_chr.h>
#elif defined(__linux__)
# define _GNU_SOURCE 1
#define _GNU_SOURCE 1
#include <time.h>
#endif
/* Define RTLAB before including OpalPrint.h for messages to be sent
@ -43,6 +44,7 @@
#include "config.h"
#include "msg.h"
#include "socket.h"
#include "utils.h"
/* This is just for initializing the shared memory access to communicate
* with the RT-LAB model. It's easier to remember the arguments like this */
@ -69,7 +71,7 @@ void Tick(int sig, siginfo_t *si, void *ptr)
OpalGetAsyncModelTime(IconCtrlStruct, &CpuTime, &ModelTime);
OpalPrint("%s: CpuTime: %llu\tModelTime: %.3f\tSequence: %hu\tValue: %.2f\n",
PROGNAME, (CpuTime - CpuTimeStart) / CPU_TICKS, ModelTime, ntohs(msg_send->sequence), msg_send->data[0].f);
PROGNAME, (CpuTime - CpuTimeStart) / CPU_TICKS, ModelTime, msg_send->sequence, msg_send->data[0].f);
}
#endif /* _DEBUG */
@ -78,8 +80,8 @@ static void *SendToIPPort(void *arg)
unsigned int SendID = 1;
unsigned int ModelState;
unsigned int i, n;
unsigned short seq = 0;
int nbSend = 0;
uint32_t seq = 0;
/* Data from OPAL-RT model */
double mdldata[MSG_VALUES];
@ -126,13 +128,17 @@ static void *SendToIPPort(void *arg)
/* Read data from the model */
OpalGetAsyncSendIconData(mdldata, mdldata_size, SendID);
/* Get current time */
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
msg.length = mdldata_size / sizeof(double);
for (i = 0; i < msg.length; i++)
msg.data[i].f = (float) mdldata[i];
/* Convert to network byte order */
msg.sequence = htonl(seq++);
msg.length = htons(msg.length);
msg.sequence = seq++;
msg.ts.sec = now.tv_sec;
msg.ts.nsec = now.tc_nsec;
/* Perform the actual write to the ip port */
if (SendPacket((char *) &msg, MSG_LEN(&msg)) < 0)
@ -206,18 +212,15 @@ static void *RecvFromIPPort(void *arg)
OpalPrint("%s: Received no data. Skipping..\n", PROGNAME);
continue;
}
/* Convert to host byte order */
msg.sequence = ntohl(msg.sequence);
msg.length = ntohs(msg.length);
/* Convert message to host endianess */
if (msg.endian != MSG_ENDIAN_HOST)
msg_swap(&msg);
if (n != MSG_LEN(&msg)) {
OpalPrint("%s: Received incoherent packet (size: %d, complete: %d)\n", PROGNAME, n, MSG_LEN(&msg));
continue;
}
if (msg.endian != MSG_ENDIAN_HOST)
msg_swap(&msg);
/* Update OPAL model */
OpalSetAsyncRecvIconStatus(msg.sequence, RecvID); /* Set the Status to the message ID */
@ -259,7 +262,7 @@ int main(int argc, char *argv[])
pthread_t tid_send, tid_recv;
pthread_attr_t attr_send, attr_recv;
OpalPrint("%s: This is a S2SS client\n", PROGNAME);
OpalPrint("%s: This is %s client version %s\n", PROGNAME, PROGNAME, VERSION);
/* Check for the proper arguments to the program */
if (argc < 4) {

View file

@ -37,6 +37,31 @@ Development tab -> Compiler -> Compiler Command (makefile) add the following com
max umber of values in UDP packets:
theres a „#define“ inside the AsyncIP implementation which must be changed accordingly.
The #define is in file: model_directory/include/config.h There you will find a directive called MAX_VALUES.
---------------------------------------------------
AsyncIP executable
- During ***Build the model*** a message should be printed:
### Created executable: AsyncIP
- After the simulation stop
AsyncIP may still stay alive after the simulation stop. You have to remove it manually because the next simulation start will not be able to start the new AsyncIP.
# Kill running AsyncIP on OPAL
1. Start putty.exe
2. Connect to OPAL by using the existing profiles
- make sure that you are in the proper folder by
$ ll
3. Kill all running processes with name 'AsyncIP'
$ killall AsyncIP
4. Logout from OPAL
$ exit
---------------------------------------------------

View file

@ -9,13 +9,13 @@ The configuration file consists of three sections:
The global section consists of some global configuration parameters:
#### `debug`
#### `debug` *(integer)*
`debug` expects an integer number (0-10) which determines the verbosity of debug messages during the execution of the server.
Use this with care! Producing a lot of IO might decrease the performance of the server.
Omitting this setting or setting it to zero will disable debug messages completely.
#### `stats`
#### `stats` *(float)*
`stats` specifies the rate in which statistics about the actives paths will be printed to the screen.
Setting this value to 5, will print 5 lines per second.
@ -25,19 +25,25 @@ A line of includes information such as:
- Messages sent
- Messaged dropped
#### `affinity`
#### `affinity` *(integer)*
The `affinity` setting allows to restrict the exeuction of the daemon to certain CPU cores.
This technique, also called 'pinning', improves the determinism of the server by isolating the daemon processes on exclusive cores.
#### `priority`
#### `priority` *(integer)*
The `priority` setting allows to adjust the scheduling priority of the deamon processes.
By default, the daemon uses a real-time optimized FIFO scheduling algorithm.
#### `name` *(integer)*
By default the `name` of a S2SS instance is equalt to the hostname of the machine it is running on.
Some node types are using this name to identify themselves agains their remotes.
An example is the `ngsi` node type which adds a metadata attribute `source` to its updates.
## Nodes
The node section is a **directory** of nodes (clients) which are connected to the S2SS instance.
The node section is a **directory** of nodes (clients) which are connected to the S2SS instance.
The directory is indexed by the name of the node:
nodes = {
@ -49,7 +55,7 @@ The directory is indexed by the name of the node:
There are multiple diffrent type of nodes. But all types have the following settings in common:
#### `type`
#### `type` *("socket" | "gtfpga" | "file" | "ngsi")*
`type` sets the type of the node. This should be one of:
- `socket` which refers to a [Socket](socket) node.
@ -62,16 +68,65 @@ Take a look a the specific pages for details.
## Paths
The path section consists of a **list** of paths.
The path section consists of a **list** of paths:
paths = [
{
in = "rtds",
out = "broker",
reverse = false,
poolsize = 32,
msgsize = 16,
combine = 4,
hook = [ "print", "ts" ]
}
]
Every path is allowed to have the following settings:
##### `in` & `out` *(string)*
The `in` and `out` settings expect the name of the source and destination node.
The `out` setting itself is allowed to be list of nodes.
This enables 1-to-n distribution of simulation data.
##### `enabled` *(boolean)*
The optional `enabled` setting can be used to temporarily disable a path.
If omitted, the path is enabled by default.
##### `reverse` *(boolean)*
By default, the path is unidirectional. Meaning, that it only forwards samples from the source to the destination.
Sometimes a bidirectional path is needed.
This can be accomplished by setting `reverse` to `true`.
##### `combine` *(integer)*
This setting allows to send multiple samples in a single message to the destination nodes. Currently this is only supported by the `file` and `socket` node-types.
The value of this setting determines how many samples will be combined into one packet.
**Important:** Please make sure that the value of this setting is smaller or equal to the `poolsize` setting of this path.
##### `rate` *(float)*
A non-zero value for this setting will change this path to an asynchronous mode.
In this mode S2SS will send with a fixed rate to all destination nodes.
It will always send the latest value it received, possible skipping values which have been received in between.
If `combine` is larger than 1, it will send the last `combine` samples at once.
**Important:** Please note that there is no correlation between the time of arrival and time of departure in this mode. It might increase the latency of this path by up to `1 / rate` seconds!
##### `poolsize` *(integer)*
Every path manages a circular buffer to keep a history of past samples. This setting specifies the size of this circular buffer.
**Important:** There are some hook functions (or the `combine` setting) which require a minimum poolsize (for example the finite-impulse-response `fir` hook).
##### `hook` *(list of strings)*
A list of hook functions which will be executed for this path.
Please consult the hook chapter of this documentation for more details.

View file

@ -13,6 +13,8 @@ S2SS is used in distributed- and co-simulation scenarios and developed for the f
The project consists of a server daemon and several client modules which are documented here.
[TOC]
### Server
The server simply acts as a gateway to forward simulation data from one client to another.
@ -21,7 +23,7 @@ Furthermore, it collects statistics, monitors the quality of service and handles
For optimal performance the server is implemented in low-level C and makes use of several Linux-specific realtime features.
The primary design goal was to make the behaviour of the system as deterministic as possible.
Therefore, it's advisable to run the server component on a [RT_PREEMPT](https://rt.wiki.kernel.org/index.php/CONFIG_PREEMPT_RT_Patch) patched version of Linux. In our environment, we use Fedora-based distribution which has been stripped to the bare minimum (no GUI, only a few background processes).
Therefore, it's advisable to run the server component on a [PREEMPT_RT](https://rt.wiki.kernel.org/index.php/CONFIG_PREEMPT_RT_Patch) patched version of Linux. In our environment, we use Fedora-based distribution which has been stripped to the bare minimum (no GUI, only a few background processes).
The server is a multi-threaded application.

View file

@ -3,7 +3,7 @@
## Operating System and Kernel
For minimum latency several kernel and driver settings can be optimized.
A [RT-preempt patched Linux](https://rt.wiki.kernel.org/index.php/Main_Page) kernel is recommended.
A [PREEMPT_RT patched Linux](https://rt.wiki.kernel.org/index.php/Main_Page) kernel is recommended.
Precompiled kernels for Fedora can be found here: http://ccrma.stanford.edu/planetccrma/software/
- Map NIC IRQs => see setting `affinity`

View file

@ -4,16 +4,14 @@ The `file` node-type can be used to log or replay samples to / from disk.
## Configuration
Every `file` node supports the following special settings:
Every `file` node can be configured to only read or write or to do both at the same time.
The node configuration is splitted in to groups: `in` and `out`.
#### `in` *(string: filesystem path)*
#### `path` *(string: filesystem path)*
Specifies the path to a file from which is written to or read from (depending in which group is used).
Specifies the path to a file which contains data for replaying.
See below for a description of the file format.
#### `out` *(string: filesystem path)*
Specifies the path to a file where samples will be written to.
This setting allows to add special paceholders for time and date values.
See [strftime(3)](http://man7.org/linux/man-pages/man3/strftime.3.html) for a list of supported placeholder.
@ -25,67 +23,108 @@ will create a file called: *path_of_working_directory*/logs/measurements_2015-08
See below for a description of the file format.
#### `file_mode` *(string)*
#### `mode` *(string)*
Specifies the mode which should be used to open the output file.
See [open(2)](http://man7.org/linux/man-pages/man2/open.2.html) for an explanation of allowed values.
The default value is `w+` which will start writing at the beginning of the file and create it in case it does not exist yet.
#### `epoch_mode` *("now"|"relative"|"absolute")*
#### `epoch_mode` *("direct"|"wait" | "relative"|"absolute")*
The *epoch* describes the point in time when the first message will be read from the file.
This setting allows to select the behaviour of the following `epoch` setting.
It can be used to adjust the point in time when the first value should be read.
The behaviour of `epoch` is depending on the value of `epoch_mode`.
- `epoch_mode = now`: The first value is read at *now* + `epoch` seconds.
- `epoch_mode = relative`: The first value is read at *start* + `epoch` seconds.
- `epoch_mode = absolute`: The first value is read at `epoch` seconds after 1970-01-01 00:00:00.
To facilitate the following description of supported `epoch_mode`'s, we will introduce some intermediate variables (timestamps).
Those variables will also been displayed during the startup phase of the server to simplify debugging.
#### `send_rate` *(float)*
- `epoch` is the value of the `epoch` setting.
- `first` is the timestamp of the first message / line in the input file.
- `offset` will be added to the timestamps in the file to obtain the real time when the message will be sent.
- `start` is the point in time when the first message will be sent (`first + offset`).
- `eta` the time to wait until the first message will be send (`start - now`)
The supported values for `epoch_mode`:
| `epoch_mode` | `offset` | `start = first + offset` |
| -----------: | :-------------------: | :----------------------: |
| `direct` | `now - first + epoch` | `now + epoch` |
| `wait` | `now + epoch` | `now + first` |
| `relative` | `epoch` | `first + epoch` |
| `absolute` | `epoch - first` | `epoch` |
#### `rate` *(float)*
By default `send_rate` has the value `0` which means that the time between consecutive samples is the same as in the `in` file based on the timestamps in the first column.
If this setting has a non-zero value, the default behaviour is overwritten with a fixed rate.
#### `split` *(integer)*
Only valid for the `out` group.
Splits the output file every `split` mega-byte. This setting will append the chunk number to the `path` setting.
Example: `data/my_measurements.log_001`
#### `splitted` *(boolean)*
Only valid for the `in` group.
Expects the input data in splitted format.
### Example
file_node = {
type = "file",
### The following settings are specific to the file node-type!! ###
mode = "w+", # The mode in which files should be opened (see open(2))
# You might want to use "a+" to append to a file
in = "logs/file_input.log", # These options specify the path prefix where the the files are stored
out = "logs/file_output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3))
epoch_mode = "now" # One of:
# now (default)
# relative
# absolute
in = {
path = "logs/input.log", # These options specify the path prefix where the the files are stored
mode = "w+", # The mode in which files should be opened (see open(2))
epoch_mode = "direct" # One of: direct (default), wait, relative, absolute
epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0).
# Consult the documentation of a full explanation
epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0):
# - epoch_mode = now: The first value is read at: _now_ + epoch seconds.
# - epoch_mode = relative: The first value is read at _start_ + `epoch` seconds.
# - epoch_mode = absolute: The first value is read at epoch seconds after 1970-01-01 00:00:00.
rate = 2.0 # A constant rate at which the lines of the input files should be read
rate = 2.0 # A constant rate at which the lines of the input files should be read
# A missing or zero value will use the timestamp in the first column
# of the file to determine the pause between consecutive lines.
splitted = false
},
out = {
path = "logs/output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3))
mode = "a+" # You might want to use "a+" to append to a file
split = 100, # Split output file every 100 MB
}
}
## File Format
This node-type uses a simple human-readable format to save samples:
This node-type uses a simple human-readable format to save samples to disk:
The format is similiar to a conventional CSV (comma seperated values) file.
Every line in a file correspondents to a message / sample of simulation data.
The columns of a line are seperated by whitespaces (tabs or spaces).
The columns are defined as follows:
1. Seconds in floating point format since 1970-01-01 00:00:00 (aka Unix epoch timestamp: `date +%s`).
2. Sequence number
3. Up to `MSG_VALUES` floating point values per sample. The values are seperated by whitespaces as well.
seconds.nanoseconds+offset(sequenceno) value0 value1 ... valueN
1. The first column contains a timestamp which is composed of up to 4 parts:
- Number of seconds after 1970-01-01 00:00:00 UTC
- A dot: '.'
- Number of nano seconds of the current second (optional)
- An offset between the point in time when a message was sent and received (optional)
- The sequence number of the message (optional)
A valid timestamp can be generated by the following Unix command: `date +%s.%N`.
*Important:* The second field is not the fractional part of the second!!!
2. Maximum `MSG_VALUES` floating point values per sample. The values are seperated by whitespaces as well.
### Example

View file

@ -20,5 +20,13 @@ Every `gtfpga` node support the following special settings:
### Example
@todo Add excerpt from example.conf
gtfpga_node = {
type = "gtfpga",
### The following settings are specific to the gtfpga node-type!! ###
slot = "01:00.0", # The PCIe slot location (see first column in 'lspci' output)
id = "1ab8:4005", # The PCIe vendor:device ID (see third column in 'lspci -n' output)
rate = 1
}

View file

@ -8,28 +8,44 @@ It's using `libcurl` and `libjansson` to communicate with the context broker ove
## Configuration
You can use the `combine` setting to send multiple samples in a vector.
Every `ngsi` node supports the following special settings:
#### `endpoint` *(string: URL)*
#### `entity_id` *(string)*
#### `entity_type` *(string)*
#### `ssl_verify` *(boolean)*
#### `timeout` *(float: seconds)*
#### `structure` *("flat" | "children")*
- `flat`:
- `children`:
#### `mapping` *(array of strings)*
Format for `structure = flat`: `"entityId(entityType).attributeName(attributeType)"`
Format for `structure = children`: `"parentId(entityType).value(attributeType)"`
Format `AttributeName(AttributeType)`
### Example
@todo add example from example.conf
ngsi_node = {
type = "ngsi",
### The following settings are specific to the ngsi node-type!! ###
endpoint = "http://46.101.131.212:1026",# The HTTP REST API endpoint of the FIRWARE context broker
entity_id = "S3_ElectricalGrid",
entity_type = "ElectricalGridMonitoring",
timeout = 5, # Timeout of HTTP request in seconds (default is 1)
verify_ssl = false, # Verification of SSL server certificates (default is true)
mapping = [ # Format: "AttributeName(AttributeType)"
PTotalLosses(MW)",
"QTotalLosses(Mvar)"
]
}
## Further reading

View file

@ -15,6 +15,18 @@ Every `opal` node supports the following special settings:
#### `reply` *(boolean)*
### Example
opal_node = { # The server can be started as an Asynchronous process
type = "opal", # from within an OPAL-RT model.
### The following settings are specific to the opal node-type!! ###
send_id = 1, # It's possible to have multiple send / recv Icons per model
recv_id = 1, # Specify the ID here.
reply = true
}
## Arguments for OPAL-RT block
RT-LAB already provides a block to establish simple TCP/IP communication: ???

View file

@ -27,7 +27,23 @@ Every `socket` node supports the following special settings:
### Example
@todo Add excerpt from example.conf
udp_node = { # The dictionary is indexed by the name of the node.
type = "socket", # Type can be one of: socket, opal, file, gtfpga, ngsi
# Start the server without arguments for a list of supported node types.
### The following settings are specific to the socket node-type!! ###
layer = "udp" # Layer can be one of:
# udp Send / recv UDP packets
# ip Send / recv IP packets
# eth Send / recv raw Ethernet frames (IEEE802.3)
local = "127.0.0.1:12001", # This node only received messages on this IP:Port pair
remote = "127.0.0.1:12000" # This node sents outgoing messages to this IP:Port pair
combine = 30 # Receive and sent 30 samples per message (multiplexing).
}
## Packet Format

View file

@ -1,7 +1,7 @@
TARGETS = server send random receive test
# Common objs
OBJS = path.o node.o hooks.o msg.o cfg.o stats.o
OBJS = path.o node.o hooks.o msg.o cfg.o
# Helper libs
OBJS += utils.o list.o hist.o log.o timing.o checks.o

View file

@ -27,6 +27,9 @@ debug = 5; # The level of verbosity for debug messages
stats = 3; # The interval in seconds to print path statistics.
# A value of 0 disables the statistics.
name = "s2ss-acs" # The name of this S2SS instance. Might by used by node-types
# to identify themselves (default is the hostname).
############ Dictionary of nodes ############
@ -84,25 +87,26 @@ nodes = {
### The following settings are specific to the file node-type!! ###
mode = "w+", # The mode in which files should be opened (see open(2))
# You might want to use "a+" to append to a file
in = "logs/file_input.log", # These options specify the path prefix where the the files are stored
out = "logs/file_output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3))
in = {
path = "logs/input.log", # These options specify the path prefix where the the files are stored
mode = "w+", # The mode in which files should be opened (see open(2))
epoch_mode = "direct" # One of: direct (default), wait, relative, absolute
epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0).
# Consult the documentation of a full explanation
epoch_mode = "now" # One of:
# now (default)
# relative
# absolute
rate = 2.0 # A constant rate at which the lines of the input files should be read
# A missing or zero value will use the timestamp in the first column
# of the file to determine the pause between consecutive lines.
splitted = false
},
out = {
path = "logs/output_%F_%T.log" # The output path accepts all format tokens of (see strftime(3))
mode = "a+" # You might want to use "a+" to append to a file
epoch = 10 # The interpretation of this value depends on epoch_mode (default is 0):
# - epoch_mode = now: The first value is read at: _now_ + epoch seconds.
# - epoch_mode = relative: The first value is read at _start_ + `epoch` seconds.
# - epoch_mode = absolute: The first value is read at epoch seconds after 1970-01-01 00:00:00.
rate = 2.0 # A constant rate at which the lines of the input files should be read
# A missing or zero value will use the timestamp in the first column
# of the file to determine the pause between consecutive lines.
split = 100, # Split output file every 100 MB
}
},
gtfpga_node = {
type = "gtfpga",
@ -120,20 +124,17 @@ nodes = {
### The following settings are specific to the ngsi node-type!! ###
endpoint = "http://46.101.131.212:1026",# The HTTP REST API endpoint of the FIRWARE context broker
entity_id = "S3_ElectricalGrid",
entity_type = "ElectricalGridMonitoring",
timeout = 5, # Timeout of HTTP request in seconds (default is 1)
structure = "children", # Structure of published context (default is "flat")
verify_ssl = false, # Verification of SSL server certificates (default is true)
mapping = [ # Example mapping for structure = flat
"rtds_sub1(V2).v2(float)", # Format: "entityId(entityType).AttributeName(AttributeType)"
"rtds_sub2(power).p1(float)"
mapping = [ # Format: "AttributeName(AttributeType)"
PTotalLosses(MW)",
"QTotalLosses(Mvar)"
]
# mapping = [ # Alternative mapping for structure = children
# "fa846ed3-5871-11e5-b0cd-ecf4bb16fe0c(GridSectionDataValue).value(float)", # Index #0
# "1d2c63e4-6130-11e5-9b0d-001c42f23160(GridSectionDataValue).value(float)" # Index #1
# .... # Index #n
# (every line correspondents to one value)
#]
}
};
@ -164,7 +165,22 @@ paths = (
in = "opal_node", # There's only a single source node allowed!
out = [ "udp_node", "tcp_node" ], # Multiple destination nodes are supported too.
hook = [ "print", "decimate" ] # Same is true for hooks.
hook = [ "print", "decimate:10" ] # Same is true for hooks.
# Multipe hook functions are executed in the order they are specified here.
},
{
in = "socket_node",
out = "file_node", # This path includes all available example hooks.
hook = [
"ts", # Replace the timestamp of messages with the time of reception
"skip_unchanged:0.1", # Skip messages whose values have not changed more than 0.1 from the previous message.
"skip_first:10", # Skip all messages which have been received in the first 10 seconds
"print", # Print all messages to stdout
"decimate:2", # Only forward every 2nd message
"convert:fixed", # Convert all values to fixed precission. Use 'float' to convert to floating point.
"fir:0" # Apply finite impulse response filter to first value.
# Coefficients are hard-coded in 'include/config.h'.
]
}
);

View file

@ -82,6 +82,15 @@ int config_parse_nodelist(config_setting_t *cfg, struct list *nodes, struct list
**/
int config_parse_hooklist(config_setting_t *cfg, struct list *hooks);
/** Parse a single hook and append it to the list.
* A hook definition is composed of the hook name and optional parameters
* seperated by a colon.
*
* Examples:
* "print:stdout"
*/
int config_parse_hook(config_setting_t *cfg, struct list *list);
/** Parse a single node and add it to the global configuration.
*
* @param cfg A libconfig object pointing to the node.

View file

@ -9,14 +9,14 @@
#ifndef _CHECKS_H_
#define _CHECKS_H_
/** Checks for realtime (RT_PREEMPT) patched kernel.
/** Checks for realtime (PREEMPT_RT) patched kernel.
*
* See https://rt.wiki.kernel.org
*
* @retval 0 Kernel is patched.
* @reval <>0 Kernel is not patched.
*/
int check_kernel_rtpreempt();
int check_kernel_rt();
/** Check if kernel command line contains "isolcpus=" option.
*

View file

@ -18,16 +18,16 @@
#endif
/** The version number of the s2ss server */
#define VERSION "v0.4-" _GIT_REV
#define VERSION "v0.5-" _GIT_REV
/** Maximum number of float values in a message */
#define MAX_VALUES 16
#define MAX_VALUES 64
/** Maximum number of messages in the circular history buffer */
#define DEFAULT_POOLSIZE 32
/** Width of log output in characters */
#define LOG_WIDTH 74
#define LOG_WIDTH 132
/** Socket priority */
#define SOCKET_PRIO 7
@ -51,29 +51,25 @@
{ "/sys/class/net/eth0/address" , "50:e5:49:eb:74:0c" }, \
{ "/etc/machine-id", "0d8399d0216314f083b9ed2053a354a8" }, \
{ "/dev/sda2", "\x53\xf6\xb5\xeb\x8b\x16\x46\xdc\x8d\x8f\x5b\x70\xb8\xc9\x1a\x2a", 0x468 } }
/* Hard coded configuration of hook functions */
#define HOOK_FIR_INDEX 0 /**< Which value inside a message should be filtered? */
/** Coefficients for simple FIR-LowPass:
* F_s = 1kHz, F_pass = 100 Hz, F_block = 300
*
* Tip: Use MATLAB's filter design tool and export coefficients
* with the integrated C-Header export
*/
#define HOOK_FIR_COEFFS { -0.003658148158728, -0.008882653268281, 0.008001024183003, \
0.08090485991761, 0.2035239551043, 0.3040703593515, \
0.3040703593515, 0.2035239551043, 0.08090485991761, \
0.008001024183003, -0.008882653268281,-0.003658148158728 }
#define HOOK_TS_INDEX -1 /**< The last value of message should be overwritten by a timestamp. */
#define HOOK_DECIMATE_RATIO 30 /**< Only forward every 30th message to the destination nodes. */
#define HOOK_DEDUP_TYPE HOOK_ASYNC
#define HOOK_DEDUP_TRESH 1e-3 /**< Do not send messages when difference of values to last message is smaller than this threshold */
/** Global configuration */
struct settings {
/** Process priority (lower is better) */
int priority;
/** Process affinity of the server and all created threads */
int affinity;
/** Debug log level */
int debug;
/** Interval for path statistics. Set to 0 to disable themo disable them. */
double stats;
int priority; /**< Process priority (lower is better) */
int affinity; /**< Process affinity of the server and all created threads */
int debug; /**< Debug log level */
double stats; /**< Interval for path statistics. Set to 0 to disable themo disable them. */
const char *name; /**< Name of the S2SS instance */
};
#endif /* _CONFIG_H_ */

View file

@ -20,29 +20,40 @@
#include "node.h"
#define FILE_MAX_PATHLEN 128
#define FILE_MAX_PATHLEN 512
enum {
FILE_READ,
FILE_WRITE
};
struct file {
FILE *in;
FILE *out;
struct file_direction {
FILE *handle; /**< libc: stdio file handle */
char *path_in;
char *path_out;
const char *mode; /**< libc: fopen() mode */
const char *fmt; /**< Format string for file name. */
const char *file_mode; /**< The mode for fopen() which is used for the out file. */
char *path; /**< Real file name */
int chunk; /**< Current chunk number. */
int split; /**< Split file every file::split mega bytes. */
} read, write;
enum epoch_mode {
EPOCH_NOW,
enum read_epoch_mode {
EPOCH_DIRECT,
EPOCH_WAIT,
EPOCH_RELATIVE,
EPOCH_ABSOLUTE
} epoch_mode; /**< Specifies how file::offset is calculated. */
} read_epoch_mode; /**< Specifies how file::offset is calculated. */
struct timespec start; /**< The first timestamp of the input file. */
struct timespec epoch; /**< The epoch timestamp from the configuration. */
struct timespec offset; /**< An offset between the timestamp in the input file and the current time */
struct timespec read_first; /**< The first timestamp in the file file::path_in */
struct timespec read_epoch; /**< The epoch timestamp from the configuration. */
struct timespec read_offset; /**< An offset between the timestamp in the input file and the current time */
double rate; /**< The sending rate. */
int tfd; /**< Timer file descriptor. Blocks until 1/rate seconds are elapsed. */
int read_sequence; /**< Sequence number of last message which has been written to file::path_out */
int read_timer; /**< Timer file descriptor. Blocks until 1 / rate seconds are elapsed. */
double read_rate; /**< The read rate. */
};
/** @see node_vtable::init */

View file

@ -38,9 +38,7 @@ struct gtfpga {
int fd_mmap; /**< File descriptor for the memory mapped PCI BAR */
void *map;
/* The following descriptor is blocking as long no interrupt was received
* or the timer has not elapsed */
int fd_irq; /**< File descriptor for the timer */
int fd_irq; /**< File descriptor for the timer. This is blocking when read(2) until a new IRQ / timer expiration. */
char *name;
double rate;

View file

@ -21,34 +21,24 @@ typedef unsigned hist_cnt_t;
/** Histogram structure used to collect statistics. */
struct hist {
/** The distance between two adjacent buckets. */
double resolution;
double resolution; /**< The distance between two adjacent buckets. */
/** The value of the highest bucket. */
double high;
/** The value of the lowest bucket. */
double low;
double high; /**< The value of the highest bucket. */
double low; /**< The value of the lowest bucket. */
/** The highest value observed (may be higher than #high). */
double highest;
/** The lowest value observed (may be lower than #low). */
double lowest;
double highest; /**< The highest value observed (may be higher than #high). */
double lowest; /**< The lowest value observed (may be lower than #low). */
double last; /**< The last value which has been put into the buckets */
/** The number of buckets in #data. */
int length;
int length; /**< The number of buckets in #data. */
/** Total number of counted values. */
hist_cnt_t total;
/** The number of values which are higher than #high. */
hist_cnt_t higher;
/** The number of values which are lower than #low. */
hist_cnt_t lower;
hist_cnt_t total; /**< Total number of counted values. */
hist_cnt_t higher; /**< The number of values which are higher than #high. */
hist_cnt_t lower; /**< The number of values which are lower than #low. */
/** Pointer to dynamically allocated array of size length. */
hist_cnt_t *data;
hist_cnt_t *data; /**< Pointer to dynamically allocated array of size length. */
/** Private variables for online variance calculation */
double _m[2], _s[2];
double _m[2], _s[2]; /**< Private variables for online variance calculation */
};
/** Initialize struct hist with supplied values and allocate memory for buckets. */

View file

@ -23,16 +23,17 @@
#include <time.h>
#define REGISTER_HOOK(name, prio, fnc, type) \
__attribute__((constructor)) void __register_ ## fnc () { \
static struct hook h = { name, prio, fnc, type }; \
list_push(&hooks, &h); \
#define REGISTER_HOOK(name, prio, fnc, type) \
__attribute__((constructor)) void __register_ ## fnc () { \
static struct hook h = { name, NULL, prio, type, NULL, fnc }; \
list_push(&hooks, &h); \
}
/* The configuration of hook parameters is done in "config.h" */
/* Forward declarations */
struct path;
struct hook;
/** This is a list of hooks which can be used in the configuration file. */
extern struct list hooks;
@ -40,34 +41,51 @@ extern struct list hooks;
/** Callback type of hook function
*
* @param p The path which is processing this message.
* @param h The hook datastructure which contains parameter, name and private context for the hook.
* @param when Provides the type of hook for which this occurence of the callback function was executed. See hook_type for possible values.
* @retval 0 Success. Continue processing and forwarding the message.
* @retval <0 Error. Drop the message.
*/
typedef int (*hook_cb_t)(struct path *p);
typedef int (*hook_cb_t)(struct path *p, struct hook *h, int when);
/** The type of a hook defines when a hook will be exectuted. */
/** The type of a hook defines when a hook will be exectuted. This is used as a bitmask. */
enum hook_type {
HOOK_PATH_START, /**< Called whenever a path is started; before threads are created. */
HOOK_PATH_STOP, /**< Called whenever a path is stopped; after threads are destoyed. */
HOOK_PATH_RESTART, /**< Called whenever a new simulation case is started. This is detected by a sequence no equal to zero. */
HOOK_PATH_START = 1 << 0, /**< Called whenever a path is started; before threads are created. */
HOOK_PATH_STOP = 1 << 1, /**< Called whenever a path is stopped; after threads are destoyed. */
HOOK_PATH_RESTART = 1 << 2, /**< Called whenever a new simulation case is started. This is detected by a sequence no equal to zero. */
HOOK_PRE, /**< Called when a new packet of messages (samples) was received. */
HOOK_POST, /**< Called after each message (sample) of a packet was processed. */
HOOK_MSG, /**< Called for each message (sample) in a packet. */
HOOK_ASYNC, /**< Called asynchronously with fixed rate (see path::rate). */
HOOK_PRE = 1 << 3, /**< Called when a new packet of messages (samples) was received. */
HOOK_POST = 1 << 4, /**< Called after each message (sample) of a packet was processed. */
HOOK_MSG = 1 << 5, /**< Called for each message (sample) in a packet. */
HOOK_ASYNC = 1 << 6, /**< Called asynchronously with fixed rate (see path::rate). */
HOOK_PERIODIC, /**< Called periodically. Period is set by global 'stats' option in the configuration file. */
HOOK_MAX
HOOK_PERIODIC = 1 << 7, /**< Called periodically. Period is set by global 'stats' option in the configuration file. */
HOOK_INIT = 1 << 8, /**< Called to allocate and init hook-private data */
HOOK_DEINIT = 1 << 9, /**< Called to free hook-private data */
/** @{ Classes of hooks */
/** Internal hooks are mandatory. */
HOOK_INTERNAL = 1 << 16,
/** Hooks which are using private data must allocate and free them propery. */
HOOK_PRIVATE = HOOK_INIT | HOOK_DEINIT,
/** All path related actions */
HOOK_PATH = HOOK_PATH_START | HOOK_PATH_STOP | HOOK_PATH_RESTART,
/** Hooks which are used to collect statistics. */
HOOK_STATS = HOOK_INTERNAL | HOOK_PRIVATE | HOOK_PATH | HOOK_MSG | HOOK_PRE | HOOK_PERIODIC,
/** All hooks */
HOOK_ALL = HOOK_INTERNAL - 1
/** @} */
};
/** Descriptor for user defined hooks. See hooks[]. */
struct hook {
/** The unique name of this hook. This must be the first member! */
const char *name;
int priority;
hook_cb_t callback;
enum hook_type type;
const char *name; /**< The unique name of this hook. This must be the first member! */
const char *parameter; /**< A parameter string for this hook. Can be used to configure the hook behaviour. */
int priority; /**< A priority to change the order of execution within one type of hook */
enum hook_type type; /**< The type of the hook as a bitfield */
void *private; /**< Private data for this hook. This pointer can be used to pass data between consecutive calls of the callback. */
hook_cb_t cb; /**< The hook callback function as a function pointer. */
};
/** The following prototypes are example hooks
@ -76,36 +94,45 @@ struct hook {
* @{
*/
/** Example hook: Drop messages whose values are similiar to the previous ones */
int hook_deduplicate(struct path *);
/** Example hook: Print the message. */
int hook_print(struct path *p);
int hook_print(struct path *p, struct hook *h, int when);
/** Example hook: Drop messages. */
int hook_decimate(struct path *p);
int hook_decimate(struct path *p, struct hook *h, int when);
/** Example hook: Convert the message values to fixed precision. */
int hook_tofixed(struct path *p);
/** Example hook: Convert the values of a message between fixed (integer) and floating point representation. */
int hook_convert(struct path *p, struct hook *h, int when);
/** Example hook: overwrite timestamp of message. */
int hook_ts(struct path *p);
int hook_ts(struct path *p, struct hook *h, int when);
/** Example hook: Finite-Impulse-Response (FIR) filter. */
int hook_fir(struct path *p);
int hook_fir(struct path *p, struct hook *h, int when);
/** Example hook: Discrete Fourier Transform */
int hook_dft(struct path *p);
/** Example hook: drop first samples after simulation restart */
int hook_skip_first(struct path *p, struct hook *h, int when);
/* The following prototypes are core hook functions */
/** Example hook: Skip messages whose values are similiar to the previous ones */
int hook_skip_unchanged(struct path *p, struct hook *h, int when);
/* The following hooks are used to implement core functionality */
/** Core hook: verify message headers. Invalid messages will be dropped. */
int hook_verify(struct path *p);
int hook_verify(struct path *p, struct hook *h, int when);
/** Core hook: reset the path in case a new simulation was started. */
int hook_restart(struct path *p);
int hook_restart(struct path *p, struct hook *h, int when);
/** Core hook: check if sequence number is correct. Otherwise message will be dropped */
int hook_drop(struct path *p);
int hook_drop(struct path *p, struct hook *h, int when);
/** Core hook: collect statistics */
int hook_stats(struct path *p, struct hook *h, int when);
/** Core hook: send path statistics to another node */
int hook_stats_send(struct path *p, struct hook *h, int when);
/** Not a hook: just prints header for periodic statistics */
void hook_stats_header();
#endif /** _HOOKS_H_ @} @} */

View file

@ -30,16 +30,12 @@ struct rtnl_link;
/** Interface data structure */
struct interface {
/** libnl3: Handle of interface */
struct rtnl_link *nl_link;
/** libnl3: Root prio qdisc */
struct rtnl_qdisc *tc_qdisc;
struct rtnl_link *nl_link; /**< libnl3: Handle of interface. */
struct rtnl_qdisc *tc_qdisc; /**< libnl3: Root priority queuing discipline (qdisc). */
/** List of IRQs of the NIC */
char irqs[IF_IRQ_MAX];
char irqs[IF_IRQ_MAX]; /**< List of IRQs of the NIC. */
/** Linked list of associated sockets */
struct list sockets;
struct list sockets; /**< Linked list of associated sockets. */
};
/** Add a new interface to the global list and lookup name, irqs...

View file

@ -14,83 +14,62 @@
#include <pthread.h>
/* Forward declarations */
struct list_elm;
struct node;
struct path;
struct interface;
struct socket;
struct gtfpga;
struct opal;
#define LIST_CHUNKSIZE 16
/** Static list initialization */
#define LIST_INIT(dtor) { \
.head = NULL, \
.tail = NULL, \
.length = 0, \
.lock = PTHREAD_MUTEX_INITIALIZER, \
.destructor = dtor \
#define LIST_INIT(dtor) { \
.start = NULL, \
.end = NULL, \
.length = 0, \
.capacity = 0, \
.lock = PTHREAD_MUTEX_INITIALIZER, \
.destructor = dtor \
}
#define FOREACH(list, elm) \
for ( struct list_elm *elm = (list)->head; \
elm; elm = elm->next )
#define FOREACH_R(list, elm) \
for ( struct list_elm *elm = (list)->tail; \
elm; elm = elm->prev )
#define list_first(list) ((list)->head)
#define list_last(list) ((list)->head)
#define list_length(list) ((list)->length)
#define list_at(list, index) ((list)->length > index ? (list)->start[index] : NULL)
#define list_first(list) list_at(list, 0)
#define list_last(list) list_at(list, (list)->length-1)
#define list_foreach(ptr, list) for (int _i = 0, _p; _p = 1, _i < (list)->length; _i++) \
for (ptr = (list)->start[_i]; _p--; )
/** Callback to destroy list elements.
*
* @param data A pointer to the data which should be freed.
*/
typedef void (*dtor_cb_t)(void *data);
typedef int (*cmp_cb_t)(void *, void *);
typedef void (*dtor_cb_t)(void *);
/** Callback to search or sort a list. */
typedef int (*cmp_cb_t)(const void *, const void *);
struct list {
struct list_elm *head, *tail;
int length;
dtor_cb_t destructor;
pthread_mutex_t lock;
};
struct list_elm {
union {
void *ptr;
struct node *node;
struct node_type *type;
struct path *path;
struct interface *interface;
struct socket *socket;
struct opal *opal;
struct gtfpga *gtfpga;
struct hook *hook;
} /* anonymous */;
int priority;
struct list_elm *prev, *next;
void **start; /**< Array of pointers to list elements */
void **end; /**< Array of pointers to list elements */
size_t capacity; /**< Size of list::start in elements */
size_t length; /**< Number of elements of list::start which are in use */
dtor_cb_t destructor; /**< A destructor which gets called for every list elements during list_destroy() */
pthread_mutex_t lock; /**< A mutex to allow thread-safe accesses */
};
/** Initialize a list */
void list_init(struct list *l, dtor_cb_t dtor);
/** Destroy a list and call destructors for all list elements */
void list_destroy(struct list *l);
/** Append an element to the end of the list */
void list_push(struct list *l, void *p);
void list_insert(struct list *l, int prio, void *p);
/** Search the list for an element whose first element is a character array which matches name.
*
* @see Only possible because of §1424 of http://c0x.coding-guidelines.com/6.7.2.1.html
*/
void * list_lookup(const struct list *l, const char *name);
void * list_lookup(struct list *l, const char *name);
void * list_search(const struct list *l, cmp_cb_t cmp, void *ctx);
void * list_search(struct list *l, cmp_cb_t cmp, void *ctx);
/** Sort the list using the quicksort algorithm of libc */
void list_sort(struct list *l, cmp_cb_t cmp);
#endif /* _LIST_H_ */

View file

@ -24,6 +24,7 @@
#define INFO ""
#define WARN YEL("Warn ")
#define ERROR RED("Error")
#define STATS MAG("Stats")
/** Change log indention for current thread.
*
@ -45,7 +46,7 @@ void log_outdent(int *);
void log_setlevel(int lvl);
/** Reset the wallclock of debug messages. */
void log_reset();
void log_init();
/** Logs variadic messages to stdout.
*
@ -77,7 +78,11 @@ void info(const char *fmt, ...)
/** Printf alike warning message. */
void warn(const char *fmt, ...)
__attribute__ ((format(printf, 1, 2)));
/** Printf alike statistics message. */
void stats(const char *fmt, ...)
__attribute__ ((format(printf, 1, 2)));
/** Print error and exit. */
void error(const char *fmt, ...)
__attribute__ ((format(printf, 1, 2)));

View file

@ -16,11 +16,21 @@
struct node;
/** Swap a message to host byte order.
/** These flags define the format which is used by msg_fscan() and msg_fprint(). */
enum msg_flags {
MSG_PRINT_NANOSECONDS = 1,
MSG_PRINT_OFFSET = 2,
MSG_PRINT_SEQUENCE = 4,
MSG_PRINT_VALUES = 8,
MSG_PRINT_ALL = 0xFF
};
/** Swaps message contents byte-order.
*
* Message can either be transmitted in little or big endian
* format. The actual endianess for a message is defined by the
* msg::byteorder field.
* msg::endian field. This covers msg::length, msg::sequence, msg::data and msg::ts fields.
* Received message are usally converted to the endianess of the host.
* This is required for further sanity checks of the sequence number
* or parsing of the data.
@ -41,21 +51,25 @@ int msg_verify(struct msg *m);
/** Print a raw message in human readable form to a file stream.
*
* @param f The file stream
* @param m A pointer to the message
* @param f The file handle from fopen() or stdout, stderr.
* @param m A pointer to the message.
* @param flags See msg_flags.
* @param offset A optional offset in seconds. Only used if flags contains MSG_PRINT_OFFSET.
* @retval 0 Success. Everything went well.
* @retval <0 Error. Something went wrong.
*/
int msg_fprint(FILE *f, struct msg *m);
int msg_fprint(FILE *f, struct msg *m, int flags, double offset);
/** Read a message from a file stream.
*
* @param f The file stream
* @param m A pointer to the message
* @param f The file handle from fopen() or stdin.
* @param m A pointer to the message.
* @param flags et MSG_PRINT_* flags for each component found in sample if not NULL. See msg_flags.
* @param offset Write offset to this pointer if not NULL.
* @retval 0 Success. Everything went well.
* @retval <0 Error. Something went wrong.
*/
int msg_fscan(FILE *f, struct msg *m);
int msg_fscan(FILE *f, struct msg *m, int *flags, double *offset);
/** Change the values of an existing message in a random fashion.
*

View file

@ -44,9 +44,10 @@
#error "Unknown byte order!"
#endif
/** The total length of a message */
/** The total size in bytes of a message */
#define MSG_LEN(msg) (4 * ((msg)->length + 4))
/** The timestamp of a message in struct timespec format */
#define MSG_TS(msg) (struct timespec) { \
.tv_sec = (msg)->ts.sec, \
.tv_nsec = (msg)->ts.nsec \
@ -81,18 +82,16 @@ struct msg
#endif
unsigned rsvd2 : 8; /**< Reserved bits */
/** The number of values in msg::data[] */
uint16_t length;
uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages */
uint16_t length; /**< The number of values in msg::data[]. Endianess is specified in msg::endian. */
uint32_t sequence; /**< The sequence number is incremented by one for consecutive messages. Endianess is specified in msg::endian. */
/** A timestamp per message */
/** A timestamp per message. Endianess is specified in msg::endian. */
struct {
uint32_t sec; /**< Seconds since 1970-01-01 00:00:00 */
uint32_t nsec; /**< Nanoseconds since 1970-01-01 00:00:00 */
uint32_t nsec; /**< Nanoseconds of the current second. */
} ts;
/** The message payload */
/** The message payload. Endianess is specified in msg::endian. */
union {
float f; /**< Floating point values (note msg::endian) */
uint32_t i; /**< Integer values (note msg::endian) */

View file

@ -32,34 +32,30 @@
struct node;
struct ngsi_mapping {
char *name;
char *type;
int index;
};
struct ngsi {
/** The NGSI context broker endpoint URL. */
const char *endpoint;
/** An optional authentication token which will be sent as HTTP header. */
const char *token;
const char *endpoint; /**< The NGSI context broker endpoint URL. */
const char *entity_id; /**< The context broker entity id related to this node */
const char *entity_type; /**< The type of the entity */
const char *access_token; /**< An optional authentication token which will be sent as HTTP header. */
/** HTTP timeout in seconds */
double timeout;
/** Boolean flag whether SSL server certificates should be verified or not. */
int ssl_verify;
double timeout; /**< HTTP timeout in seconds */
double rate; /**< Rate used for polling. */
/** Structure of published entitites */
enum ngsi_structure {
NGSI_FLAT,
NGSI_CHILDREN
} structure;
/** List of HTTP request headers for libcurl */
struct curl_slist *headers;
/** libcurl handle */
CURL *curl;
int tfd; /**< Timer */
int ssl_verify; /**< Boolean flag whether SSL server certificates should be verified or not. */
/** The complete JSON tree which will be used for contextUpdate requests */
json_t *context;
/** A mapping between indices of the S2SS messages and the attributes in ngsi::context */
json_t **context_map;
/** The number of mappings in ngsi::context_map */
int context_len;
struct curl_slist *headers; /**< List of HTTP request headers for libcurl */
CURL *curl; /**< libcurl: handle */
struct list mapping; /**< A mapping between indices of the S2SS messages and the attributes in ngsi::context */
};
/** Initialize global NGSI settings and maps shared memory regions.

View file

@ -27,14 +27,18 @@
#include "list.h"
/* Helper macros for virtual node type */
#define node_type(n) ((n)->vt->type)
#define node_print(n) ((n)->vt->print(n))
#define node_type(n) ((n)->_vt->type)
#define node_parse(n, cfg) ((n)->_vt->parse(cfg, n))
#define node_print(n) ((n)->_vt->print(n))
#define node_read(n, p, ps, f, c) ((n)->vt->read(n, p, ps, f, c))
#define node_write(n, p, ps, f, c) ((n)->vt->write(n, p, ps, f, c))
#define node_read(n, p, ps, f, c) ((n)->_vt->read(n, p, ps, f, c))
#define node_write(n, p, ps, f, c) ((n)->_vt->write(n, p, ps, f, c))
#define node_read_single(n, m) ((n)->vt->read(n, m, 1, 0, 1))
#define node_write_single(n, m) ((n)->vt->write(n, m, 1, 0, 1))
#define node_open(n) ((n)->_vt->open(n))
#define node_close(n) ((n)->_vt->close(n))
#define node_read_single(n, m) ((n)->_vt->read(n, m, 1, 0, 1))
#define node_write_single(n, m) ((n)->_vt->write(n, m, 1, 0, 1))
#define REGISTER_NODE_TYPE(type, name, fnc) \
@ -49,21 +53,18 @@ __attribute__((constructor)) void __register_node_ ## fnc () { \
extern struct list node_types;
/** C++ like vtable construct for node_types
* @todo Add comments
*/
/** C++ like vtable construct for node_types */
struct node_type {
/** The unique name of this node. This must be allways the first member! */
char *name;
/** Node type */
const char *name;
enum {
BSD_SOCKET, /**< BSD Socket API */
LOG_FILE, /**< File IO */
OPAL_ASYNC, /**< OPAL-RT Asynchronous Process Api */
GTFPGA, /**< Xilinx ML507 GTFPGA card */
NGSI /**< NGSI 9/10 HTTP RESTful API (FIRWARE ContextBroker) */
} type;
} type; /**< Node type */
/** Parse node connection details.
*
@ -130,10 +131,28 @@ struct node_type {
*/
int (*write)(struct node *n, struct msg *pool, int poolsize, int first, int cnt);
/** Global initialization per node type.
*
* This callback is invoked once per node-type.
*
* @param argc Number of arguments passed to the server executable (see main()).
* @param argv Array of arguments passed to the server executable (see main()).
* @param set Global settings.
* @retval 0 Success. Everything went well.
* @retval <0 Error. Something went wrong.
*/
int (*init)(int argc, char *argv[], struct settings *set);
/** Global de-initialization per node type.
*
* This callback is invoked once per node-type.
*
* @retval 0 Success. Everything went well.
* @retval <0 Error. Something went wrong.
*/
int (*deinit)();
int refcnt;
int refcnt; /**< Reference counter: how many nodes are using this node-type? */
};
/** The data structure for a node.
@ -143,28 +162,23 @@ struct node_type {
*/
struct node
{
/** A short identifier of the node, only used for configuration and logging */
char *name;
/** How many paths are sending / receiving from this node? */
int refcnt;
/** Number of messages to send / recv at once (scatter / gather) */
int combine;
/** CPU Affinity of this node */
int affinity;
const char *name; /**< A short identifier of the node, only used for configuration and logging */
int refcnt; /**< How many paths are sending / receiving from this node? */
int combine; /**< Number of messages to send / recv at once (scatter / gather) */
int affinity; /**< CPU Affinity of this node */
struct node_type *_vt; /**< C++ like virtual function call table */
/** C++ like virtual function call table */
struct node_type * vt;
/** Virtual data (used by vtable functions) */
union {
struct socket *socket;
struct opal *opal;
struct gtfpga *gtfpga;
struct file *file;
struct ngsi *ngsi;
};
}; /** Virtual data (used by struct node::_vt functions) */
/** A pointer to the libconfig object which instantiated this node */
config_setting_t *cfg;
config_setting_t *cfg; /**< A pointer to the libconfig object which instantiated this node */
};
/** Initialize node type subsystems.
@ -218,7 +232,7 @@ int node_stop(struct node *n);
void node_reverse(struct node *n);
/** Create a node by allocating dynamic memory. */
struct node * node_create();
struct node * node_create(struct node_type *vt);
/** Destroy node by freeing dynamically allocated memory.
*

View file

@ -31,63 +31,47 @@
*/
struct path
{
/** Pointer to the incoming node */
struct node *in;
/** Pointer to the first outgoing node.
* Usually this is only a pointer to the first list element of path::destinations. */
struct node *out;
struct node *in; /**< Pointer to the incoming node */
struct node *out; /**< Pointer to the first outgoing node ( path::out == list_first(path::destinations) */
/** List of all outgoing nodes */
struct list destinations;
/** List of function pointers to hooks */
struct list hooks[HOOK_MAX];
struct list destinations; /**< List of all outgoing nodes */
struct list hooks; /**< List of function pointers to hooks */
/** Timer file descriptor for fixed rate sending */
int tfd;
/** Send messages with a fixed rate over this path */
double rate;
int tfd; /**< Timer file descriptor for fixed rate sending */
double rate; /**< Send messages with a fixed rate over this path */
/** Size of the history buffer in number of messages */
int poolsize;
/** A circular buffer of past messages */
struct msg *pool;
int msgsize; /**< Maximum number of values per message for this path */
int poolsize; /**< Size of the history buffer in number of messages */
struct msg *pool; /**< A circular buffer of past messages */
/** A pointer to the last received message */
struct msg *current;
/** A pointer to the previously received message */
struct msg *previous;
struct msg *current; /**< A pointer to the last received message */
struct msg *previous; /**< A pointer to the previously received message */
/** The thread id for this path */
pthread_t recv_tid;
/** A second thread id for fixed rate sending thread */
pthread_t sent_tid;
/** A pointer to the libconfig object which instantiated this path */
config_setting_t *cfg;
/* The following fields are mostly managed by hook_ functions */
/** Histogram of sequence number displacement of received messages */
struct hist hist_sequence;
/** Histogram for delay of received messages */
struct hist hist_delay;
/** Histogram for inter message delay (time between received messages) */
struct hist hist_gap;
/** Last message received */
struct timespec ts_recv;
/** Last message sent */
struct timespec ts_sent;
pthread_t recv_tid; /**< The thread id for this path */
pthread_t sent_tid; /**< A second thread id for fixed rate sending thread */
/** Counter for sent messages to all outgoing nodes */
unsigned int sent;
/** Counter for received messages from all incoming nodes */
unsigned int received;
/** Counter for invalid messages */
unsigned int invalid;
/** Counter for skipped messages due to hooks */
unsigned int skipped;
/** Counter for dropped messages due to reordering */
unsigned int dropped;
config_setting_t *cfg; /**< A pointer to the libconfig object which instantiated this path */
/** The following fields are mostly managed by hook_ functions @{ */
struct hist hist_owd; /**< Histogram for one-way-delay (OWD) of received messages */
struct hist hist_gap_msg; /**< Histogram for inter message timestamps (as sent by remote) */
struct hist hist_gap_recv; /**< Histogram for inter message arrival time (as seen by this instance) */
struct hist hist_gap_seq; /**< Histogram of sequence number displacement of received messages */
struct timespec ts_recv; /**< Last message received */
struct timespec ts_sent; /**< Last message sent */
struct timespec ts_last; /**< Previous message received (old value of path::ts_recv) */
unsigned int sent; /**< Counter for sent messages to all outgoing nodes */
unsigned int received; /**< Counter for received messages from all incoming nodes */
unsigned int invalid; /**< Counter for invalid messages */
unsigned int skipped; /**< Counter for skipped messages due to hooks */
unsigned int dropped; /**< Counter for dropped messages due to reordering */
unsigned int overrun; /**< Counter of overruns for fixed-rate sending */
/** @} */
};
/** Create a path by allocating dynamic memory. */
@ -117,15 +101,6 @@ int path_start(struct path *p);
*/
int path_stop(struct path *p);
/** Reset internal counters and histogram of a path.
*
* @param p A pointer to the path structure.
* @retval 0 Success. Everything went well.
* @retval <0 Error. Something went wrong.
*/
int path_reset(struct path *p);
/** Show some basic statistics for a path.
*
* @param p A pointer to the path structure.

View file

@ -1,36 +0,0 @@
/** Hook functions to collect statistics
*
* @file
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2014-2015, Institute for Automation of Complex Power Systems, EONERC
* This file is part of S2SS. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
*********************************************************************************/
#ifndef _STATS_H_
#define _STATS_H_
/* Forward declarations */
struct path;
/** Print a table header for statistics printed by stats_line() */
void stats_header();
/** Print a single line of stats including received, sent, invalid and dropped packet counters */
int stats_line(struct path *p);
int stats_show(struct path *p);
/** Update histograms */
int stats_collect(struct path *p);
/** Create histograms */
int stats_start(struct path *p);
/** Destroy histograms */
int stats_stop(struct path *p);
/** Reset all statistic counters to zero */
int stats_reset(struct path *p);
#endif /* _STATS_H_ */

View file

@ -39,6 +39,9 @@ struct timespec time_diff(struct timespec *start, struct timespec *end);
/** Get sum of two timespec structs */
struct timespec time_add(struct timespec *start, struct timespec *end);
/** Return current time as a struct timespec. */
struct timespec time_now();
/** Return the diffrence off two timestamps as double value in seconds. */
double time_delta(struct timespec *start, struct timespec *end);
@ -48,10 +51,4 @@ double time_to_double(struct timespec *ts);
/** Convert double containing seconds after 1970 to timespec. */
struct timespec time_from_double(double secs);
/** Read a timestamp from a file with the format: "secs.nanosecs\t" */
int time_fscan(FILE *f, struct timespec *ts);
/** Write a timestamp to a file with the format: "secs.nanosecs\t" */
int time_fprint(FILE *f, struct timespec *ts);
#endif /* _TIMING_H_ */

View file

@ -101,6 +101,10 @@ char * strcatf(char **dest, const char *fmt, ...)
char * vstrcatf(char **dest, const char *fmt, va_list va)
__attribute__ ((format(printf, 2, 0)));
/** Format a struct timespec date similar to strftime() */
int strftimespec(char *s, size_t max, const char *format, struct timespec *ts)
__attribute__ ((format(strftime, 3, 0)));
/** Convert integer to cpu_set_t.
*
* @param set A cpu bitmask
@ -111,6 +115,9 @@ cpu_set_t to_cpu_set(int set);
/** Allocate and initialize memory. */
void * alloc(size_t bytes);
/** Allocate and copy memory. */
void * memdup(const void *src, size_t bytes);
/** Call quit() in the main thread. */
void die();
@ -126,10 +133,6 @@ int version_compare(struct version *a, struct version *b);
/** Parse a dotted version string. */
int version_parse(const char *s, struct version *v);
/** Format a struct timespec date similar to strftime() */
int strftimespec(char *s, uint max, const char *format, struct timespec *ts)
__attribute__ ((format(strftime, 3, 0)));
/** Check assertion and exit if failed. */
#define assert(exp) do { \
if (!EXPECT(exp, 0)) \

View file

@ -8,6 +8,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "utils.h"
#include "list.h"
@ -87,6 +88,11 @@ int config_parse_global(config_setting_t *cfg, struct settings *set)
config_setting_lookup_int(cfg, "debug", &set->debug);
config_setting_lookup_float(cfg, "stats", &set->stats);
if (!config_setting_lookup_string(cfg, "name", &set->name)) {
set->name = alloc(128); /** @todo missing free */
gethostname((char *) set->name, 128);
}
log_setlevel(set->debug);
return 0;
@ -95,36 +101,34 @@ int config_parse_global(config_setting_t *cfg, struct settings *set)
int config_parse_path(config_setting_t *cfg,
struct list *paths, struct list *nodes, struct settings *set)
{
config_setting_t *cfg_out, *cfg_hook;
const char *in;
int enabled;
int reverse;
int enabled, reverse;
struct path *p = path_create();
/* Input node */
struct config_setting_t *cfg_in = config_setting_get_member(cfg, "in");
if (!cfg_in || config_setting_type(cfg_in) != CONFIG_TYPE_STRING)
cerror(cfg, "Invalid input node for path");
if (!config_setting_lookup_string(cfg, "in", &in))
cerror(cfg, "Missing input node for path");
in = config_setting_get_string(cfg_in);
p->in = list_lookup(nodes, in);
if (!p->in)
cerror(cfg_in, "Invalid input node '%s'", in);
error("Invalid input node '%s'", in);
/* Output node(s) */
struct config_setting_t *cfg_out = config_setting_get_member(cfg, "out");
cfg_out = config_setting_get_member(cfg, "out");
if (cfg_out)
config_parse_nodelist(cfg_out, &p->destinations, nodes);
if (list_length(&p->destinations) >= 1)
p->out = list_first(&p->destinations)->node;
else
cerror(cfg, "Missing output node for path");
p->out = (struct node *) list_first(&p->destinations);
/* Optional settings */
struct config_setting_t *cfg_hook = config_setting_get_member(cfg, "hook");
cfg_hook = config_setting_get_member(cfg, "hook");
if (cfg_hook)
config_parse_hooklist(cfg_hook, p->hooks);
config_parse_hooklist(cfg_hook, &p->hooks);
/* Initialize hooks and their private data / parameters */
path_run_hook(p, HOOK_INIT);
if (!config_setting_lookup_bool(cfg, "enabled", &enabled))
enabled = 1;
@ -134,17 +138,21 @@ int config_parse_path(config_setting_t *cfg,
p->rate = 0; /* disabled */
if (!config_setting_lookup_int(cfg, "poolsize", &p->poolsize))
p->poolsize = DEFAULT_POOLSIZE;
if (!config_setting_lookup_int(cfg, "msgsize", &p->msgsize))
p->msgsize = MAX_VALUES;
p->cfg = cfg;
if (enabled) {
p->in->refcnt++;
p->in->vt->refcnt++;
p->in->_vt->refcnt++;
FOREACH(&p->destinations, it) {
it->node->refcnt++;
it->node->vt->refcnt++;
list_foreach(struct node *node, &p->destinations) {
node->refcnt++;
node->_vt->refcnt++;
}
list_push(paths, p);
if (reverse) {
if (list_length(&p->destinations) > 1)
@ -152,20 +160,30 @@ int config_parse_path(config_setting_t *cfg,
struct path *r = path_create();
r->in = p->out; /* Swap in/out */
/* Swap in/out */
r->in = p->out;
r->out = p->in;
list_push(&r->destinations, r->out);
/* Increment reference counters */
r->in->refcnt++;
r->in->_vt->refcnt++;
r->out->refcnt++;
r->in->vt->refcnt++;
r->out->vt->refcnt++;
r->out->_vt->refcnt++;
if (cfg_hook)
config_parse_hooklist(cfg_hook, &r->hooks);
/* Initialize hooks and their private data / parameters */
path_run_hook(r, HOOK_INIT);
r->rate = p->rate;
r->poolsize = p->poolsize;
r->msgsize = p->msgsize;
list_push(paths, r);
}
list_push(paths, p);
}
else {
char *buf = path_print(p);
@ -220,39 +238,54 @@ int config_parse_nodelist(config_setting_t *cfg, struct list *list, struct list
return 0;
}
int config_parse_hooklist(config_setting_t *cfg, struct list *list) {
const char *str;
const struct hook *hook;
int config_parse_node(config_setting_t *cfg, struct list *nodes, struct settings *set)
{
const char *type, *name;
int ret;
struct node *n;
struct node_type *vt;
/* Required settings */
if (!config_setting_lookup_string(cfg, "type", &type))
cerror(cfg, "Missing node type");
name = config_setting_name(cfg);
vt = list_lookup(&node_types, type);
if (!vt)
cerror(cfg, "Invalid type for node '%s'", config_setting_name(cfg));
n = node_create(vt);
n->name = name;
n->cfg = cfg;
ret = node_parse(n, cfg);
if (ret)
cerror(cfg, "Failed to parse node '%s'", n->name);
if (!config_setting_lookup_int(cfg, "combine", &n->combine))
n->combine = 1;
if (!config_setting_lookup_int(cfg, "affinity", &n->affinity))
n->affinity = set->affinity;
list_push(nodes, n);
list_push(&vt->nodes, n);
return ret;
}
int config_parse_hooklist(config_setting_t *cfg, struct list *list) {
switch (config_setting_type(cfg)) {
case CONFIG_TYPE_STRING:
str = config_setting_get_string(cfg);
if (str) {
hook = list_lookup(&hooks, str);
if (hook)
list_insert(&list[hook->type], hook->priority, hook->callback);
else
cerror(cfg, "Unknown hook function '%s'", str);
}
else
cerror(cfg, "Invalid hook function");
config_parse_hook(cfg, list);
break;
case CONFIG_TYPE_ARRAY:
for (int i = 0; i<config_setting_length(cfg); i++) {
config_setting_t *elm = config_setting_get_elem(cfg, i);
str = config_setting_get_string(elm);
if (str) {
hook = list_lookup(&hooks, str);
if (hook)
list_insert(&list[hook->type], hook->priority, hook->callback);
else
cerror(elm, "Invalid hook function '%s'", str);
}
else
cerror(cfg, "Invalid hook function");
}
for (int i = 0; i < config_setting_length(cfg); i++)
config_parse_hook(config_setting_get_elem(cfg, i), list);
break;
default:
@ -262,36 +295,27 @@ int config_parse_hooklist(config_setting_t *cfg, struct list *list) {
return 0;
}
int config_parse_node(config_setting_t *cfg, struct list *nodes, struct settings *set)
int config_parse_hook(config_setting_t *cfg, struct list *list)
{
const char *type;
int ret;
struct hook *hook, *copy;
const char *name = config_setting_get_string(cfg);
if (!name)
cerror(cfg, "Invalid hook function");
char *param = strchr(name, ':');
if (param) { /* Split hook line */
*param = '\0';
param++;
}
hook = list_lookup(&hooks, name);
if (!hook)
cerror(cfg, "Unknown hook function '%s'", name);
copy = memdup(hook, sizeof(struct hook));
copy->parameter = param;
list_push(list, copy);
struct node *n = node_create();
/* Required settings */
n->cfg = cfg;
n->name = config_setting_name(cfg);
if (!n->name)
cerror(cfg, "Missing node name");
if (!config_setting_lookup_string(cfg, "type", &type))
cerror(cfg, "Missing node type");
if (!config_setting_lookup_int(cfg, "combine", &n->combine))
n->combine = 1;
if (!config_setting_lookup_int(cfg, "affinity", &n->combine))
n->affinity = set->affinity;
n->vt = list_lookup(&node_types, type);
if (!n->vt)
cerror(cfg, "Invalid type for node '%s'", n->name);
ret = n->vt->parse(cfg, n);
if (!ret)
list_push(nodes, n);
return ret;
return 0;
}

View file

@ -34,7 +34,7 @@ int check_kernel_version()
return version_compare(&current, &required) < 0;
}
int check_kernel_rtpreempt()
int check_kernel_rt()
{
return access(SYSFS_PATH "/kernel/realtime", R_OK);
}

View file

@ -26,124 +26,232 @@ int file_deinit()
return 0; /* nothing todo here */
}
char * file_print(struct node *n)
static char * file_format_name(const char *format, struct timespec *ts)
{
struct file *f = n->file;
char *buf = NULL;
struct tm tm;
char *buf = alloc(FILE_MAX_PATHLEN);
/* Convert time */
gmtime_r(&ts->tv_sec, &tm);
return strcatf(&buf, "in=%s, out=%s, mode=%s, rate=%.1f, epoch_mode=%u, epoch=%.0f",
f->path_in, f->path_out, f->file_mode, f->rate, f->epoch_mode, time_to_double(&f->epoch));
strftime(buf, FILE_MAX_PATHLEN, format, &tm);
return buf;
}
static FILE * file_reopen(struct file_direction *dir)
{
char buf[FILE_MAX_PATHLEN];
const char *path = buf;
/* Append chunk number to filename */
if (dir->chunk >= 0)
snprintf(buf, FILE_MAX_PATHLEN, "%s_%03u", dir->path, dir->chunk);
else
path = dir->path;
if (dir->handle)
fclose(dir->handle);
return fopen(path, dir->mode);
}
static int file_parse_direction(config_setting_t *cfg, struct file *f, int d)
{
struct file_direction *dir = (d == FILE_READ) ? &f->read : &f->write;
if (!config_setting_lookup_string(cfg, "path", &dir->fmt))
return -1;
if (!config_setting_lookup_string(cfg, "mode", &dir->mode))
dir->mode = (d == FILE_READ) ? "r" : "w+";
return 0;
}
int file_parse(config_setting_t *cfg, struct node *n)
{
struct file *f = alloc(sizeof(struct file));
config_setting_t *cfg_in, *cfg_out;
cfg_out = config_setting_get_member(cfg, "out");
if (cfg_out) {
if (file_parse_direction(cfg_out, f, FILE_WRITE))
cerror(cfg_out, "Failed to parse output file for node '%s'", n->name);
const char *out, *in;
const char *epoch_mode;
double epoch_flt;
if (config_setting_lookup_string(cfg, "out", &out)) {
time_t t = time(NULL);
struct tm *tm = localtime(&t);
f->path_out = alloc(FILE_MAX_PATHLEN);
if (strftime(f->path_out, FILE_MAX_PATHLEN, out, tm) == 0)
cerror(cfg, "Invalid path for output");
/* More write specific settings */
if (!config_setting_lookup_int(cfg_out, "split", &f->write.split))
f->write.split = 0; /* Save all samples in a single file */
}
if (config_setting_lookup_string(cfg, "in", &in))
f->path_in = strdup(in);
if (!config_setting_lookup_string(cfg, "file_mode", &f->file_mode))
f->file_mode = "w+";
cfg_in = config_setting_get_member(cfg, "in");
if (cfg_in) {
if (file_parse_direction(cfg_in, f, FILE_READ))
cerror(cfg_in, "Failed to parse input file for node '%s'", n->name);
if (!config_setting_lookup_float(cfg, "send_rate", &f->rate))
f->rate = 0; /* Disable fixed rate sending. Using timestamps of file instead */
/* More read specific settings */
if (!config_setting_lookup_bool(cfg_in, "splitted", &f->read.split))
f->read.split = 0; /* Save all samples in a single file */
if (!config_setting_lookup_float(cfg_in, "rate", &f->read_rate))
f->read_rate = 0; /* Disable fixed rate sending. Using timestamps of file instead */
double epoch_flt;
if (!config_setting_lookup_float(cfg_in, "epoch", &epoch_flt))
epoch_flt = 0;
f->read_epoch = time_from_double(epoch_flt);
if (config_setting_lookup_float(n->cfg, "epoch", &epoch_flt))
f->epoch = time_from_double(epoch_flt);
const char *epoch_mode;
if (!config_setting_lookup_string(cfg_in, "epoch_mode", &epoch_mode))
epoch_mode = "direct";
if (!config_setting_lookup_string(n->cfg, "epoch_mode", &epoch_mode))
epoch_mode = "now";
if (!strcmp(epoch_mode, "now"))
f->epoch_mode = EPOCH_NOW;
else if (!strcmp(epoch_mode, "relative"))
f->epoch_mode = EPOCH_RELATIVE;
else if (!strcmp(epoch_mode, "absolute"))
f->epoch_mode = EPOCH_ABSOLUTE;
else
cerror(n->cfg, "Invalid value '%s' for setting 'epoch_mode'", epoch_mode);
if (!strcmp(epoch_mode, "direct"))
f->read_epoch_mode = EPOCH_DIRECT;
else if (!strcmp(epoch_mode, "wait"))
f->read_epoch_mode = EPOCH_WAIT;
else if (!strcmp(epoch_mode, "relative"))
f->read_epoch_mode = EPOCH_RELATIVE;
else if (!strcmp(epoch_mode, "absolute"))
f->read_epoch_mode = EPOCH_ABSOLUTE;
else
cerror(cfg_in, "Invalid value '%s' for setting 'epoch_mode'", epoch_mode);
}
n->file = f;
return 0;
}
char * file_print(struct node *n)
{
struct file *f = n->file;
char *buf = NULL;
if (f->read.fmt) {
const char *epoch_str = NULL;
switch (f->read_epoch_mode) {
case EPOCH_DIRECT: epoch_str = "direct"; break;
case EPOCH_WAIT: epoch_str = "wait"; break;
case EPOCH_RELATIVE: epoch_str = "relative"; break;
case EPOCH_ABSOLUTE: epoch_str = "absolute"; break;
}
strcatf(&buf, "in=%s, epoch_mode=%s, epoch=%.2f, ",
f->read.path ? f->read.path : f->read.fmt,
epoch_str,
time_to_double(&f->read_epoch)
);
if (f->read_rate)
strcatf(&buf, "rate=%.1f, ", f->read_rate);
}
if (f->write.fmt) {
strcatf(&buf, "out=%s, mode=%s, ",
f->write.path ? f->write.path : f->write.fmt,
f->write.mode
);
}
if (f->read_first.tv_sec || f->read_first.tv_nsec)
strcatf(&buf, "first=%.2f, ", time_to_double(&f->read_first));
if (f->read_offset.tv_sec || f->read_offset.tv_nsec)
strcatf(&buf, "offset=%.2f, ", time_to_double(&f->read_offset));
if ((f->read_first.tv_sec || f->read_first.tv_nsec) &&
(f->read_offset.tv_sec || f->read_offset.tv_nsec)) {
struct timespec eta, now = time_now();
eta = time_add(&f->read_first, &f->read_offset);
eta = time_diff(&now, &eta);
if (eta.tv_sec || eta.tv_nsec)
strcatf(&buf, "eta=%.2f sec, ", time_to_double(&eta));
}
if (strlen(buf) > 2)
buf[strlen(buf) - 2] = 0;
return buf;
}
int file_open(struct node *n)
{
struct file *f = n->file;
struct timespec now = time_now();
if (f->path_in) {
f->in = fopen(f->path_in, "r");
if (!f->in)
serror("Failed to open file for reading: '%s'", f->path_in);
if (f->read.fmt) {
/* Prepare file name */
f->read.chunk = f->read.split ? 0 : -1;
f->read.path = file_format_name(f->read.fmt, &now);
/* Open file */
f->read.handle = file_reopen(&f->read);
if (!f->read.handle)
serror("Failed to open file for reading: '%s'", f->read.path);
f->tfd = timerfd_create(CLOCK_REALTIME, 0);
if (f->tfd < 0)
/* Create timer */
f->read_timer = timerfd_create(CLOCK_REALTIME, 0);
if (f->read_timer < 0)
serror("Failed to create timer");
/* Arm the timer */
if (f->rate) {
/* Send with fixed rate */
/* Arm the timer with a fixed rate */
if (f->read_rate) {
struct itimerspec its = {
.it_interval = time_from_double(1 / f->rate),
.it_value = { 1, 0 },
.it_interval = time_from_double(1 / f->read_rate),
.it_value = { 0, 1 },
};
int ret = timerfd_settime(f->tfd, 0, &its, NULL);
int ret = timerfd_settime(f->read_timer, 0, &its, NULL);
if (ret)
serror("Failed to start timer");
}
else {
/* Get current time */
struct timespec now, tmp;
clock_gettime(CLOCK_REALTIME, &now);
/* Get current time */
struct timespec now = time_now();
/* Get timestamp of first sample */
time_fscan(f->in, &f->start); rewind(f->in);
/* Get timestamp of first line */
struct msg m;
int ret = msg_fscan(f->read.handle, &m, NULL, NULL); rewind(f->read.handle);
if (ret < 0)
error("Failed to read first timestamp of node '%s'", n->name);
f->read_first = MSG_TS(&m);
/* Set offset depending on epoch_mode */
switch (f->epoch_mode) {
case EPOCH_NOW: /* read first value at f->now + f->epoch */
tmp = time_diff(&f->start, &now);
f->offset = time_add(&tmp, &f->epoch);
break;
case EPOCH_RELATIVE: /* read first value at f->start + f->epoch */
f->offset = f->epoch;
break;
case EPOCH_ABSOLUTE: /* read first value at f->epoch */
f->offset = time_diff(&f->start, &f->epoch);
break;
}
/* Set read_offset depending on epoch_mode */
switch (f->read_epoch_mode) {
case EPOCH_DIRECT: /* read first value at now + epoch */
f->read_offset = time_diff(&f->read_first, &now);
f->read_offset = time_add(&f->read_offset, &f->read_epoch);
break;
tmp = time_add(&f->start, &f->offset);
tmp = time_diff(&now, &tmp);
debug(5, "Opened file '%s' as input for node '%s': start=%.2f, offset=%.2f, eta=%.2f",
f->path_in, n->name,
time_to_double(&f->start),
time_to_double(&f->offset),
time_to_double(&tmp)
);
case EPOCH_WAIT: /* read first value at now + first + epoch */
f->read_offset = now;
f->read_offset = time_add(&f->read_offset, &f->read_epoch);
break;
case EPOCH_RELATIVE: /* read first value at first + epoch */
f->read_offset = f->read_epoch;
break;
case EPOCH_ABSOLUTE: /* read first value at f->read_epoch */
f->read_offset = time_diff(&f->read_first, &f->read_epoch);
break;
}
}
if (f->path_out) {
f->out = fopen(f->path_out, f->file_mode);
if (!f->out)
serror("Failed to open file for writing: '%s'", f->path_out);
if (f->write.fmt) {
/* Prepare file name */
f->write.chunk = f->write.split ? 0 : -1;
f->write.path = file_format_name(f->write.fmt, &now);
/* Open file */
f->write.handle = file_reopen(&f->write);
if (!f->write.handle)
serror("Failed to open file for writing: '%s'", f->write.path);
}
return 0;
@ -152,51 +260,73 @@ int file_open(struct node *n)
int file_close(struct node *n)
{
struct file *f = n->file;
free(f->read.path);
free(f->write.path);
if (f->tfd)
close(f->tfd);
if (f->in)
fclose(f->in);
if (f->out)
fclose(f->out);
if (f->read_timer)
close(f->read_timer);
if (f->read.handle)
fclose(f->read.handle);
if (f->write.handle)
fclose(f->write.handle);
return 0;
}
int file_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt)
{
int i = 0;
int values, flags, i = 0;
struct file *f = n->file;
if (f->in) {
if (f->read.handle) {
for (i = 0; i < cnt; i++) {
struct msg *cur = &pool[(first+i) % poolsize];
if (f->rate) {
/* Wait until epoch for the first time only */
if (ftell(f->in) == 0) {
struct timespec until = time_add(&f->start, &f->offset);
if (timerfd_wait_until(f->tfd, &until))
serror("Failed to wait for timer");
}
/* Wait with fixed rate delay */
else {
if (timerfd_wait(f->tfd) < 0)
serror("Failed to wait for timer");
/* Get message and timestamp */
retry: values = msg_fscan(f->read.handle, cur, &flags, NULL);
if (values < 0) {
if (feof(f->read.handle)) {
if (f->read.split) {
f->read.chunk++;
f->read.handle = file_reopen(&f->read);
if (!f->read.handle)
return 0;
info("Open new input chunk of node '%s': chunk=%u", n->name, f->read.chunk);
}
else {
info("Rewind input file of node '%s'", n->name);
rewind(f->read.handle);
goto retry;
}
}
else
warn("Failed to read messages from node '%s': reason=%d", n->name, values);
msg_fscan(f->in, cur);
return 0;
}
else {
struct timespec until;
/* Get message and timestamp */
msg_fscan(f->in, cur);
/* Wait for next message / sampe */
until = time_add(&MSG_TS(cur), &f->offset);
if (timerfd_wait_until(f->tfd, &until) < 0)
/* Fix missing sequence no */
cur->sequence = f->read_sequence = (flags & MSG_PRINT_SEQUENCE) ? cur->sequence : f->read_sequence + 1;
if (!f->read_rate || ftell(f->read.handle) == 0) {
struct timespec until = time_add(&MSG_TS(cur), &f->read_offset);
if (timerfd_wait_until(f->read_timer, &until) < 0)
serror("Failed to wait for timer");
/* Update timestamp */
cur->ts.sec = until.tv_sec;
cur->ts.nsec = until.tv_nsec;
}
else { /* Wait with fixed rate delay */
if (timerfd_wait(f->read_timer) < 0)
serror("Failed to wait for timer");
/* Update timestamp */
struct timespec now = time_now();
cur->ts.sec = now.tv_sec;
cur->ts.nsec = now.tv_nsec;
}
}
}
@ -211,15 +341,23 @@ int file_write(struct node *n, struct msg *pool, int poolsize, int first, int cn
int i = 0;
struct file *f = n->file;
if (f->out) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
for (i = 0; i < cnt; i++)
msg_fprint(f->out, &pool[(first+i) % poolsize]);
if (f->write.handle) {
for (i = 0; i < cnt; i++) {
/* Split file if requested */
if ((f->write.split > 0) && ftell(f->write.handle) > f->write.split * (1 << 20)) {
f->write.chunk++;
f->write.handle = file_reopen(&f->write);
info("Splitted output file for node '%s': chunk=%u", n->name, f->write.chunk);
}
struct msg *m = &pool[(first+i) % poolsize];
msg_fprint(f->write.handle, m, MSG_PRINT_ALL & ~MSG_PRINT_OFFSET, 0);
}
fflush(f->write.handle);
}
else
error("Can not write to node '%s", n->name);
error("Can not write to node '%s'", n->name);
return i;
}

View file

@ -31,6 +31,9 @@ static void gtfpga_debug(char *msg, ...) {
int gtfpga_init(int argc, char * argv[], struct settings *set)
{
if (check_root())
error("The gtfpga node-type requires superuser privileges!");
pacc = pci_alloc(); /* Get the pci_access structure */
if (!pacc)
error("Failed to allocate PCI access structure");
@ -60,12 +63,6 @@ int gtfpga_parse(config_setting_t *cfg, struct node *n)
const char *slot, *id, *err;
config_setting_t *cfg_slot, *cfg_id;
/* Checks */
if (n->combine != 1) {
config_setting_t *cfg_combine = config_setting_get_member(cfg, "combine");
cerror(cfg_combine, "The GTFPGA node type does not support combining!");
}
pci_filter_init(NULL, &g->filter);
cfg_slot = config_setting_get_member(cfg, "slot");
@ -212,7 +209,7 @@ int gtfpga_open(struct node *n)
struct itimerspec its = {
.it_interval = time_from_double(1 / g->rate),
.it_value = { 1, 0 }
.it_value = { 0, 1 }
};
ret = timerfd_settime(g->fd_irq, 0, &its, NULL);
if (ret)
@ -244,6 +241,9 @@ int gtfpga_read(struct node *n, struct msg *pool, int poolsize, int first, int c
struct gtfpga *g = n->gtfpga;
struct msg *m = &pool[first % poolsize];
if (cnt != 1)
error("The GTFPGA node type does not support combining!");
uint64_t runs;
read(g->fd_irq, &runs, sizeof(runs));
@ -259,8 +259,10 @@ int gtfpga_read(struct node *n, struct msg *pool, int poolsize, int first, int c
int gtfpga_write(struct node *n, struct msg *pool, int poolsize, int first, int cnt)
{
// struct gtfpga *g = n->gtfpga;
// struct msg *m = &pool[first % poolsize];
if (cnt != 1)
error("The GTFPGA node type does not support combining!");
return 0;
}

View file

@ -38,6 +38,8 @@ void hist_destroy(struct hist *h)
void hist_put(struct hist *h, double value)
{
int idx = INDEX(h, value);
h->last = value;
/* Update min/max */
if (value > h->highest)
@ -101,23 +103,16 @@ double hist_stddev(struct hist *h)
void hist_print(struct hist *h)
{ INDENT
info("Total: %u values", h->total);
info("Highest value: %f", h->highest);
info("Lowest value: %f", h->lowest);
info("Mean: %f", hist_mean(h));
info("Variance: %f", hist_var(h));
info("Standard derivation: %f", hist_stddev(h));
if (h->higher > 0)
warn("Missed: %u values above %f", h->higher, h->high);
if (h->lower > 0)
warn("Missed: %u values below %f", h->lower, h->low);
stats("Counted values: %u (%u between %f and %f)", h->total, h->total-h->higher-h->lower, h->high, h->low);
stats("Highest: %f Lowest: %f", h->highest, h->lowest);
stats("Mu: %f Sigma2: %f Sigma: %f", hist_mean(h), hist_var(h), hist_stddev(h));
if (h->total - h->higher - h->lower > 0) {
hist_plot(h);
char *buf = hist_dump(h);
info(buf);
stats("Matlab: %s", buf);
free(buf);
hist_plot(h);
}
}
@ -135,13 +130,16 @@ void hist_plot(struct hist *h)
}
/* Print plot */
info("%3s | %9s | %5s | %s", "#", "Value", "Occur", "Plot");
stats("%9s | %5s | %s", "Value", "Count", "Plot");
line();
for (int i = 0; i < h->length; i++) {
int bar = HIST_HEIGHT * ((double) h->data[i] / max);
double value = VAL(h, i);
int cnt = h->data[i];
int bar = HIST_HEIGHT * ((double) cnt / max);
info("%3u | %+5.2e | " "%5u" " | %.*s", i, VAL(h, i), h->data[i], bar, buf);
if (value > h->lowest || value < h->highest)
stats("%+9g | " "%5u" " | %.*s", value, cnt, bar, buf);
}
}
@ -165,7 +163,7 @@ void hist_matlab(struct hist *h, FILE *f)
fprintf(f, "%lu = struct( ", time(NULL));
fprintf(f, "'min', %f, 'max', %f, ", h->low, h->high);
fprintf(f, "'ok', %u, too_high', %u, 'too_low', %u, ", h->total, h->higher, h->lower);
fprintf(f, "'total', %u, higher', %u, 'lower', %u, ", h->total, h->higher, h->lower);
fprintf(f, "'highest', %f, 'lowest', %f, ", h->highest, h->lowest);
fprintf(f, "'mean', %f, ", hist_mean(h));
fprintf(f, "'var', %f, ", hist_var(h));

View file

@ -22,62 +22,28 @@
#include "path.h"
#include "utils.h"
/* Some hooks can be configured by constants in te file "config.h" */
extern struct list nodes;
struct list hooks;
REGISTER_HOOK("deduplicate", 99, hook_deduplicate, HOOK_DEDUP_TYPE)
int hook_deduplicate(struct path *p)
{
int ret = 0;
#if HOOK_DEDUP_TYPE == HOOK_ASYNC
/** Thread local storage (TLS) is used to maintain a copy of the last run of the hook */
static __thread struct msg previous = MSG_INIT(0);
struct msg *prev = &previous;
#else
struct msg *prev = p->previous;
#endif
struct msg *cur = p->current;
for (int i = 0; i < MIN(cur->length, prev->length); i++) {
if (fabs(cur->data[i].f - prev->data[i].f) > HOOK_DEDUP_TRESH)
goto out;
}
ret = -1; /* no appreciable change in values, we will drop the packet */
out:
#if HOOK_DEDUP_TYPE == HOOK_ASYNC
memcpy(prev, cur, sizeof(struct msg)); /* save current message for next run */
#endif
return ret;
}
REGISTER_HOOK("print", 99, hook_print, HOOK_MSG)
int hook_print(struct path *p)
int hook_print(struct path *p, struct hook *h, int when)
{
struct msg *m = p->current;
struct timespec ts = MSG_TS(m);
double offset = time_delta(&MSG_TS(m), &p->ts_recv);
int flags = MSG_PRINT_ALL;
fprintf(stdout, "%.3e+", time_delta(&ts, &p->ts_recv)); /* Print delay */
msg_fprint(stdout, m);
return 0;
}
REGISTER_HOOK("tofixed", 99, hook_tofixed, HOOK_MSG)
int hook_tofixed(struct path *p)
{
struct msg *m = p->current;
for (int i = 0; i < m->length; i++)
m->data[i].i = m->data[i].f * 1e3;
/* We dont show the offset if its to large */
if (offset > 1e9)
flags &= ~MSG_PRINT_OFFSET;
msg_fprint(stdout, m, flags, offset);
return 0;
}
REGISTER_HOOK("ts", 99, hook_ts, HOOK_MSG)
int hook_ts(struct path *p)
int hook_ts(struct path *p, struct hook *h, int when)
{
struct msg *m = p->current;
@ -87,59 +53,223 @@ int hook_ts(struct path *p)
return 0;
}
REGISTER_HOOK("fir", 99, hook_fir, HOOK_MSG)
int hook_fir(struct path *p)
{
/** Coefficients for simple FIR-LowPass:
* F_s = 1kHz, F_pass = 100 Hz, F_block = 300
*
* Tip: Use MATLAB's filter design tool and export coefficients
* with the integrated C-Header export
*/
static const double coeffs[] = HOOK_FIR_COEFFS;
REGISTER_HOOK("skip_unchanged", 99, hook_skip_unchanged, HOOK_PRIVATE | HOOK_ASYNC)
int hook_skip_unchanged(struct path *p, struct hook *h, int when)
{
struct private {
double threshold;
struct msg previous;
} *private = h->private;
/** Per path thread local storage for unfiltered sample values.
* The message ringbuffer (p->pool & p->current) will contain filtered data!
*/
static __thread double *past = NULL;
/** @todo Avoid dynamic allocation at runtime */
if (!past)
alloc(p->poolsize * sizeof(double));
/* Current value of interest */
float *cur = &p->current->data[HOOK_FIR_INDEX].f;
/* Save last sample, unfiltered */
past[p->received % p->poolsize] = *cur;
switch (when) {
case HOOK_INIT:
private = h->private = alloc(sizeof(struct private));
if (!h->parameter)
error("Missing parameter for hook 'deduplication'");
/* Reset accumulator */
*cur = 0;
private->threshold = strtof(h->parameter, NULL);
if (!private->threshold)
error("Failed to parse parameter '%s' for hook 'deduplication'", h->parameter);
break;
case HOOK_DEINIT:
free(private);
break;
case HOOK_ASYNC: {
int ret = 0;
struct msg *prev = &private->previous;
struct msg *cur = p->current;
/* FIR loop */
for (int i = 0; i < MIN(ARRAY_LEN(coeffs), p->poolsize); i++)
*cur += coeffs[i] * past[p->received+p->poolsize-i];
for (int i = 0; i < MIN(cur->length, prev->length); i++) {
if (fabs(cur->data[i].f - prev->data[i].f) > private->threshold)
goto out;
}
ret = -1; /* no appreciable change in values, we will drop the packet */
out: memcpy(prev, cur, sizeof(struct msg)); /* save current message for next run */
return ret;
}
}
return 0;
}
REGISTER_HOOK("convert", 99, hook_convert, HOOK_PRIVATE | HOOK_MSG)
int hook_convert(struct path *p, struct hook *h, int when)
{
struct private {
enum { TO_FIXED, TO_FLOAT } mode;
} *private = h->private;
switch (when) {
case HOOK_INIT:
private = h->private = alloc(sizeof(struct private));
if (!h->parameter)
error("Missing parameter for hook 'deduplication'");
if (!strcmp(h->parameter, "fixed"))
private->mode = TO_FIXED;
else if (!strcmp(h->parameter, "float"))
private->mode = TO_FLOAT;
else
error("Invalid parameter '%s' for hook 'convert'", h->parameter);
break;
case HOOK_DEINIT:
free(private);
break;
case HOOK_MSG: {
struct msg *m = p->current;
for (int i = 0; i < m->length; i++) {
switch (private->mode) {
/** @todo allow precission to be configured via parameter */
case TO_FIXED: m->data[i].i = m->data[i].f * 1e3; break;
case TO_FLOAT: m->data[i].f = m->data[i].i; break;
}
}
break;
}
}
return 0;
}
REGISTER_HOOK("decimate", 99, hook_decimate, HOOK_POST)
int hook_decimate(struct path *p)
REGISTER_HOOK("fir", 99, hook_fir, HOOK_PRIVATE | HOOK_MSG)
int hook_fir(struct path *p, struct hook *h, int when)
{
/* Only sent every HOOK_DECIMATE_RATIO'th message */
return p->received % HOOK_DECIMATE_RATIO;
/** @todo make this configurable via hook parameters */
const static double coeffs[] = HOOK_FIR_COEFFS;
struct private {
double *coeffs;
double *history;
int index;
} *private = h->private;
switch (when) {
case HOOK_INIT:
if (!h->parameter)
error("Missing parameter for hook 'fir'");
private = h->private = alloc(sizeof(struct private));
private->coeffs = memdup(coeffs, sizeof(coeffs));
private->history = alloc(sizeof(coeffs));
private->index = strtol(h->parameter, NULL, 10);
if (!private->index)
error("Invalid parameter '%s' for hook 'fir'", h->parameter);
break;
case HOOK_DEINIT:
free(private->coeffs);
free(private->history);
free(private);
break;
case HOOK_MSG: {
/* Current value of interest */
float *cur = &p->current->data[private->index].f;
/* Save last sample, unfiltered */
private->history[p->received % p->poolsize] = *cur;
/* Reset accumulator */
*cur = 0;
/* FIR loop */
for (int i = 0; i < MIN(ARRAY_LEN(coeffs), p->poolsize); i++)
*cur += private->coeffs[i] * private->history[p->received+p->poolsize-i];
break;
}
}
return 0;
}
REGISTER_HOOK("dft", 99, hook_dft, HOOK_POST)
int hook_dft(struct path *p)
REGISTER_HOOK("decimate", 99, hook_decimate, HOOK_PRIVATE | HOOK_POST)
int hook_decimate(struct path *p, struct hook *h, int when)
{
return 0; /** @todo Implement */
struct private {
long ratio;
} *private = h->private;
switch (when) {
case HOOK_INIT:
if (!h->parameter)
error("Missing parameter for hook 'decimate'");
private = h->private = alloc(sizeof(struct private));
private->ratio = strtol(h->parameter, NULL, 10);
if (!private->ratio)
error("Invalid parameter '%s' for hook 'decimate'", h->parameter);
break;
case HOOK_DEINIT:
free(private);
break;
case HOOK_POST:
return p->received % private->ratio;
}
return 0;
}
/** System hooks */
REGISTER_HOOK("skip_first", 99, hook_skip_first, HOOK_PRIVATE | HOOK_POST | HOOK_PATH )
int hook_skip_first(struct path *p, struct hook *h, int when)
{
struct private {
double wait; /**< Number of seconds to wait until first message is not skipped */
struct timespec started; /**< Timestamp of last simulation restart */
} *private = h->private;
switch (when) {
case HOOK_INIT:
if (!h->parameter)
error("Missing parameter for hook 'skip_first'");
int hook_restart(struct path *p)
private = h->private = alloc(sizeof(struct private));
private->wait = strtof(h->parameter, NULL);
if (!private->wait)
error("Invalid parameter '%s' for hook 'skip_first'", h->parameter);
break;
case HOOK_DEINIT:
free(private);
break;
case HOOK_PATH_RESTART:
private->started = p->ts_recv;
break;
case HOOK_PATH_START:
private->started = time_now();
break;
case HOOK_POST: {
double delta = time_delta(&private->started, &p->ts_recv);
return delta < private->wait
? -1 /* skip */
: 0; /* send */
}
}
return 0;
}
REGISTER_HOOK("restart", 1, hook_restart, HOOK_INTERNAL | HOOK_MSG)
int hook_restart(struct path *p, struct hook *h, int when)
{
if (p->current->sequence == 0 &&
p->previous->sequence <= UINT32_MAX - 32) {
@ -148,26 +278,34 @@ int hook_restart(struct path *p)
buf, p->previous->sequence, p->current->sequence);
free(buf);
path_reset(p);
p->sent =
p->invalid =
p->skipped =
p->dropped = 0;
p->received = 1;
if (path_run_hook(p, HOOK_PATH_RESTART))
return -1;
}
return 0;
}
int hook_verify(struct path *p)
REGISTER_HOOK("verify", 2, hook_verify, HOOK_INTERNAL | HOOK_MSG)
int hook_verify(struct path *p, struct hook *h, int when)
{
int reason = msg_verify(p->current);
if (reason) {
p->invalid++;
warn("Received invalid message (reason=%d)", reason);
warn("Received invalid message (reason = %d)", reason);
return -1;
}
return 0;
}
int hook_drop(struct path *p)
REGISTER_HOOK("drop", 3, hook_drop, HOOK_INTERNAL | HOOK_MSG)
int hook_drop(struct path *p, struct hook *h, int when)
{
int dist = p->current->sequence - (int32_t) p->previous->sequence;
if (dist <= 0 && p->received > 1) {
@ -177,3 +315,145 @@ int hook_drop(struct path *p)
else
return 0;
}
REGISTER_HOOK("stats", 2, hook_stats, HOOK_STATS)
int hook_stats(struct path *p, struct hook *h, int when)
{
switch (when) {
case HOOK_INIT:
/** @todo Allow configurable bounds for histograms */
hist_create(&p->hist_gap_seq, -HIST_SEQ, +HIST_SEQ, 1);
hist_create(&p->hist_owd, 0, 1, 100e-3);
hist_create(&p->hist_gap_msg, 90e-3, 110e-3, 1e-3);
hist_create(&p->hist_gap_recv, 90e-3, 110e-3, 1e-3);
break;
case HOOK_DEINIT:
hist_destroy(&p->hist_gap_seq);
hist_destroy(&p->hist_owd);
hist_destroy(&p->hist_gap_msg);
hist_destroy(&p->hist_gap_recv);
break;
case HOOK_PRE:
/* Exclude first message from statistics */
if (p->received > 0) {
double gap = time_delta(&p->ts_last, &p->ts_recv);
hist_put(&p->hist_gap_recv, gap);
}
case HOOK_MSG: {
struct msg *prev = p->previous, *cur = p->current;
/* Exclude first message from statistics */
if (p->received > 0) {
int dist = cur->sequence - (int32_t) prev->sequence;
double delay = time_delta(&MSG_TS(cur), &p->ts_recv);
double gap = time_delta(&MSG_TS(prev), &MSG_TS(cur));
hist_put(&p->hist_gap_msg, gap);
hist_put(&p->hist_gap_seq, dist);
hist_put(&p->hist_owd, delay);
}
break;
}
case HOOK_PATH_STOP:
if (p->hist_owd.total) { info("One-way delay:"); hist_print(&p->hist_owd); }
if (p->hist_gap_recv.total){ info("Inter-message arrival time:"); hist_print(&p->hist_gap_recv); }
if (p->hist_gap_msg.total) { info("Inter-message ts gap:"); hist_print(&p->hist_gap_msg); }
if (p->hist_gap_seq.total) { info("Inter-message sequence number gaps:"); hist_print(&p->hist_gap_seq); }
break;
case HOOK_PATH_RESTART:
hist_reset(&p->hist_owd);
hist_reset(&p->hist_gap_seq);
hist_reset(&p->hist_gap_msg);
hist_reset(&p->hist_gap_recv);
break;
case HOOK_PERIODIC: {
char *buf = path_print(p);
if (p->received > 1)
stats("%-40s|%10.2g|%10.2f|%10u|%10u|%10u|%10u|%10u|%10u|%10u|", buf,
p->hist_owd.last, 1 / p->hist_gap_msg.last,
p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun, list_length(p->current)
);
else
stats("%-40s|%10s|%10s|%10u|%10u|%10u|%10u|%10u|%10u|%10s|", buf, "", "",
p->sent, p->received, p->dropped, p->skipped, p->invalid, p->overrun, ""
);
free(buf);
break;
}
}
return 0;
}
void hook_stats_header()
{
#define UNIT(u) "(" YEL(u) ")"
stats("%-40s|%19s|%19s|%19s|%19s|%19s|%19s|%19s|%10s|%10s|", "Source " MAG("=>") " Destination",
"OWD" UNIT("S") " ",
"Rate" UNIT("p/S") " ",
"Sent" UNIT("p") " ",
"Recv" UNIT("p") " ",
"Drop" UNIT("p") " ",
"Skip" UNIT("p") " ",
"Inval" UNIT("p") " ",
"Overuns ",
"Values "
);
line();
}
REGISTER_HOOK("stats_send", 99, hook_stats_send, HOOK_PRIVATE | HOOK_MSG)
int hook_stats_send(struct path *p, struct hook *h, int when)
{
struct private {
struct node *dest;
int ratio;
} *private = h->private;
switch (when) {
case HOOK_INIT:
if (!h->parameter)
error("Missing parameter for hook 'stats_send'");
private = h->private = alloc(sizeof(struct private));
private->dest = list_lookup(&nodes, h->parameter);
if (!private->dest)
error("Invalid destination node '%s' for hook 'stats_send'", h->parameter);
break;
case HOOK_DEINIT:
free(private);
break;
case HOOK_MSG: {
struct msg m = MSG_INIT(0);
m.data[m.length++].f = p->sent;
m.data[m.length++].f = p->received;
m.data[m.length++].f = p->invalid;
m.data[m.length++].f = p->skipped;
m.data[m.length++].f = p->dropped;
m.data[m.length++].f = p->hist_owd.last,
m.data[m.length++].f = p->hist_gap_msg.last;
m.data[m.length++].f = p->hist_gap_recv.last;
/* Send single message with statistics to destination node */
node_write_single(private->dest, &m);
break;
}
}
return 0;
}

View file

@ -34,7 +34,11 @@ struct interface * if_create(struct rtnl_link *link)
debug(3, "Created interface '%s'", rtnl_link_get_name(i->nl_link));
if_get_irqs(i);
int n = if_get_irqs(i);
if (n > 0)
debug(6, "Found %u IRQs for interface '%s'", n, rtnl_link_get_name(i->nl_link));
else
warn("Did not found any interrupts for interface '%s'", rtnl_link_get_name(i->nl_link));
list_init(&i->sockets, NULL);
list_push(&interfaces, i);
@ -54,7 +58,7 @@ void if_destroy(struct interface *i)
int if_start(struct interface *i, int affinity)
{
info("Starting interface '%s' which is used by %u sockets", rtnl_link_get_name(i->nl_link), list_length(&i->sockets));
info("Starting interface '%s' which is used by %zu sockets", rtnl_link_get_name(i->nl_link), list_length(&i->sockets));
{ INDENT
/* Set affinity for network interfaces (skip _loopback_ dev) */
@ -62,8 +66,7 @@ int if_start(struct interface *i, int affinity)
/* Assign fwmark's to socket nodes which have netem options */
int ret, mark = 0;
FOREACH(&i->sockets, it) {
struct socket *s = it->socket;
list_foreach(struct socket *s, &i->sockets) {
if (s->tc_qdisc)
s->mark = 1 + mark++;
}
@ -86,8 +89,7 @@ int if_start(struct interface *i, int affinity)
error("Failed to setup priority queuing discipline: %s", nl_geterror(ret));
/* Create netem qdisks and appropriate filter per netem node */
FOREACH(&i->sockets, it) {
struct socket *s = it->socket;
list_foreach(struct socket *s, &i->sockets) {
if (s->tc_qdisc) {
ret = tc_mark(i, &s->tc_classifier, TC_HANDLE(1, s->mark), s->mark);
if (ret)
@ -136,7 +138,7 @@ int if_get_egress(struct sockaddr *sa, struct rtnl_link **link)
? nl_addr_build(sin->sin_family, &sin->sin_addr.s_addr, sizeof(sin->sin_addr.s_addr))
: nl_addr_build(sin6->sin6_family, sin6->sin6_addr.s6_addr, sizeof(sin6->sin6_addr));
ifindex = nl_get_egress(addr);
ifindex = nl_get_egress(addr); nl_addr_put(addr);
if (ifindex < 0)
error("Netlink error: %s", nl_geterror(ifindex));
break;
@ -177,8 +179,6 @@ int if_get_irqs(struct interface *i)
closedir(dir);
}
debug(6, "Found %u IRQs for interface '%s'", n, rtnl_link_get_name(i->nl_link));
return 0;
}
@ -208,9 +208,9 @@ int if_set_affinity(struct interface *i, int affinity)
struct interface * if_lookup_index(int index)
{
FOREACH(&interfaces, it) {
if (rtnl_link_get_ifindex(it->interface->nl_link) == index)
return it->interface;
list_foreach(struct interface *i, &interfaces) {
if (rtnl_link_get_ifindex(i->nl_link) == index)
return i;
}
return NULL;

View file

@ -8,9 +8,9 @@
* Unauthorized copying of this file, via any medium is strictly prohibited.
*********************************************************************************/
#include <stdlib.h>
#include <string.h>
#include "utils.h"
#include "list.h"
void list_init(struct list *l, dtor_cb_t dtor)
@ -19,24 +19,28 @@ void list_init(struct list *l, dtor_cb_t dtor)
l->destructor = dtor;
l->length = 0;
l->head = NULL;
l->tail = NULL;
l->capacity = 0;
l->start = NULL;
l->end = NULL;
}
void list_destroy(struct list *l)
{
pthread_mutex_lock(&l->lock);
struct list_elm *elm = l->head;
while (elm) {
struct list_elm *tmp = elm;
elm = elm->next;
if (l->destructor)
l->destructor(tmp->ptr);
free(tmp);
if (l->destructor) {
list_foreach(void *p, l)
l->destructor(p);
}
free(l->start);
l->start =
l->end = NULL;
l->length =
l->capacity = 0;
pthread_mutex_unlock(&l->lock);
pthread_mutex_destroy(&l->lock);
@ -44,83 +48,54 @@ void list_destroy(struct list *l)
void list_push(struct list *l, void *p)
{
struct list_elm *e = alloc(sizeof(struct list_elm));
pthread_mutex_lock(&l->lock);
e->ptr = p;
e->prev = l->tail;
e->next = NULL;
if (l->tail)
l->tail->next = e;
if (l->head)
l->head->prev = e;
else
l->head = e;
l->tail = e;
/* Resize array if out of capacity */
if (l->end == l->start + l->capacity) {
l->capacity += LIST_CHUNKSIZE;
l->start = realloc(l->start, l->capacity * sizeof(void *));
}
l->start[l->length] = p;
l->length++;
l->end = &l->start[l->length];
pthread_mutex_unlock(&l->lock);
}
void list_insert(struct list *l, int prio, void *p)
void * list_lookup(struct list *l, const char *name)
{
struct list_elm *d;
struct list_elm *e = alloc(sizeof(struct list_elm));
int cmp(const void *a, const void *b) {
return strcmp(*(char **) a, b);
}
e->priority = prio;
e->ptr = p;
return list_search(l, cmp, (void *) name);
}
void * list_search(struct list *l, cmp_cb_t cmp, void *ctx)
{
pthread_mutex_lock(&l->lock);
void *e;
list_foreach(e, l) {
if (!cmp(e, ctx))
goto out;
}
e = NULL; /* not found */
out: pthread_mutex_unlock(&l->lock);
return e;
}
void list_sort(struct list *l, cmp_cb_t cmp)
{
int cmp_helper(const void *a, const void *b) {
return cmp(*(void **) a, *(void **) b);
}
pthread_mutex_lock(&l->lock);
/* Search for first entry with higher priority */
for (d = l->head; d && d->priority < prio; d = d->next);
/* Insert new element in front of d */
e->next = d;
if (d) { /* Between or Head */
e->prev = d->prev;
if (d == l->head) /* Head */
l->head = e;
else /* Between */
d->prev = e;
}
else { /* Tail or Head */
e->prev = l->tail;
if (l->length == 0) /* List was empty */
l->head = e;
else
l->tail->next = e;
l->tail = e;
}
l->length++;
qsort(l->start, l->length, sizeof(void *), cmp_helper);
pthread_mutex_unlock(&l->lock);
}
void * list_lookup(const struct list *l, const char *name)
{
FOREACH(l, it) {
if (!strcmp(*(char **) it->ptr, name))
return it->ptr;
}
return NULL;
}
void * list_search(const struct list *l, cmp_cb_t cmp, void *ctx)
{
FOREACH(l, it) {
if (!cmp(it->ptr, ctx))
return it->ptr;
}
return NULL;
}
}

View file

@ -54,9 +54,9 @@ void log_setlevel(int lvl)
debug(10, "Switched to debug level %u", level);
}
void log_reset()
void log_init()
{
clock_gettime(CLOCK_REALTIME, &epoch);
epoch = time_now();
debug(10, "Debug clock resetted");
}
@ -71,11 +71,10 @@ void log_print(const char *lvl, const char *fmt, ...)
void log_vprint(const char *lvl, const char *fmt, va_list ap)
{
struct timespec ts;
struct timespec ts = time_now();
char *buf = alloc(512);
/* Timestamp */
clock_gettime(CLOCK_REALTIME, &ts);
strcatf(&buf, "%10.3f ", time_delta(&epoch, &ts));
/* Severity */
@ -137,6 +136,15 @@ void warn(const char *fmt, ...)
va_end(ap);
}
void stats(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_vprint(STATS, fmt, ap);
va_end(ap);
}
void error(const char *fmt, ...)
{
va_list ap;

View file

@ -14,6 +14,7 @@
#include <byteswap.h>
#elif defined(__PPC__) /* Xilinx toolchain */
#include <xil_io.h>
#define bswap_16(x) Xil_EndianSwap16(x)
#define bswap_32(x) Xil_EndianSwap32(x)
#endif
@ -23,8 +24,12 @@
void msg_swap(struct msg *m)
{
int i;
for (i = 0; i < m->length; i++)
m->length = bswap_16(m->length);
m->sequence = bswap_32(m->sequence);
m->ts.sec = bswap_32(m->ts.sec);
m->ts.nsec = bswap_32(m->ts.nsec);
for (int i = 0; i < m->length; i++)
m->data[i].i = bswap_32(m->data[i].i);
m->endian ^= 1;
@ -44,15 +49,23 @@ int msg_verify(struct msg *m)
return 0;
}
int msg_fprint(FILE *f, struct msg *m)
int msg_fprint(FILE *f, struct msg *m, int flags, double offset)
{
if (m->endian != MSG_ENDIAN_HOST)
msg_swap(m);
fprintf(f, "%u", m->ts.sec);
if (flags & MSG_PRINT_NANOSECONDS)
fprintf(f, ".%09u", m->ts.nsec);
if (flags & MSG_PRINT_OFFSET)
fprintf(f, "%+g", offset);
if (flags & MSG_PRINT_SEQUENCE)
fprintf(f, "(%u)", m->sequence);
fprintf(f, "%10u.%09u\t%hu", m->ts.sec, m->ts.nsec, m->sequence);
for (int i = 0; i < m->length; i++)
fprintf(f, "\t%.6f", m->data[i].f);
if (flags & MSG_PRINT_VALUES) {
for (int i = 0; i < m->length; i++)
fprintf(f, "\t%.6f", m->data[i].f);
}
fprintf(f, "\n");
@ -60,33 +73,95 @@ int msg_fprint(FILE *f, struct msg *m)
}
/** @todo Currently only floating point values are supported */
int msg_fscan(FILE *f, struct msg *m)
int msg_fscan(FILE *f, struct msg *m, int *fl, double *off)
{
char line[MSG_VALUES * 16];
char *next, *ptr = line;
if (fgets(line, sizeof(line), f) == NULL)
return -1; /* An error occured */
m->ts.sec = (uint32_t) strtoul(ptr, &ptr, 10); ptr++;
m->ts.nsec = (uint32_t) strtoul(ptr, &ptr, 10);
m->sequence = (uint16_t) strtoul(ptr, &ptr, 10);
char *end, *ptr = line;
int flags = 0;
double offset;
m->version = MSG_VERSION;
m->endian = MSG_ENDIAN_HOST;
m->length = 0;
m->rsvd1 = 0;
m->rsvd2 = 0;
m->rsvd1 = m->rsvd2 = 0;
/* Format: Seconds.NanoSeconds+Offset(SequenceNumber) Value1 Value2 ...
* RegEx: (\d+(?:\.\d+)?)([-+]\d+(?:\.\d+)?(?:e[+-]?\d+)?)?(?:\((\d+)\))?
*
* Please note that only the seconds and at least one value are mandatory
*/
while (m->length < MSG_VALUES) {
m->data[m->length].f = strtod(ptr, &next);
skip: if (fgets(line, sizeof(line), f) == NULL)
return -1; /* An error occured */
if (next == ptr)
break;
/* Skip whitespaces, empty and comment lines */
for (ptr = line; isspace(*ptr); ptr++);
if (*ptr == '\0' || *ptr == '#')
goto skip;
ptr = next;
m->length++;
/* Mandatory: seconds */
m->ts.sec = (uint32_t) strtoul(ptr, &end, 10);
if (ptr == end)
return -2;
/* Optional: nano seconds */
if (*end == '.') {
ptr = end + 1;
m->ts.nsec = (uint32_t) strtoul(ptr, &end, 10);
if (ptr != end)
flags |= MSG_PRINT_NANOSECONDS;
else
return -3;
}
else
m->ts.nsec = 0;
/* Optional: offset / delay */
if (*end == '+' || *end == '-') {
ptr = end + 1;
offset = strtof(ptr, &end); /* offset is ignored for now */
if (ptr != end)
flags |= MSG_PRINT_OFFSET;
else
return -4;
}
/* Optional: sequence number */
if (*end == '(') {
ptr = end + 1;
m->sequence = (uint16_t) strtoul(ptr, &end, 10);
if (ptr != end && *end == ')')
flags |= MSG_PRINT_SEQUENCE;
else {
info(end);
return -5;
}
end = end + 1;
}
else
m->sequence = 0;
for ( m->length = 0, ptr = end;
m->length < MSG_VALUES;
m->length++, ptr = end) {
/** @todo We only support floating point values at the moment */
m->data[m->length].f = strtod(ptr, &end);
if (end == ptr) /* there are no valid FP values anymore */
break;
}
if (m->length > 0)
flags |= MSG_PRINT_VALUES;
if (fl)
*fl = flags;
if (off && (flags & MSG_PRINT_OFFSET))
*off = offset;
return m->length;
}

View file

@ -19,10 +19,16 @@
#include <curl/curl.h>
#include <uuid/uuid.h>
#include <jansson.h>
#include <math.h>
#include <pthread.h>
#include "ngsi.h"
#include "utils.h"
#include "timing.h"
extern struct settings settings;
#if 0 /* unused at the moment */
static json_t * json_uuid()
{
char eid[37];
@ -34,17 +40,12 @@ static json_t * json_uuid()
return json_string(eid);
}
static json_t * json_date(struct timespec *ts)
{
struct timespec tsp;
if (!ts) {
clock_gettime(CLOCK_REALTIME, &tsp);
ts = &tsp;
}
// Example: 2015-09-21T11:42:25+02:00
char date[64];
strftimespec(date, sizeof(date), "%FT%T%z", ts);
strftimespec(date, sizeof(date), "%FT%T.%u%z", ts);
return json_string(date);
}
@ -64,6 +65,144 @@ static json_t * json_lookup(json_t *array, char *key, char *needle)
return NULL;
}
#endif
static json_t* json_entity(struct ngsi *i, struct msg *pool, int poolsize, int first, int cnt)
{
json_t *attributes = json_array();
list_foreach(struct ngsi_mapping *map, &i->mapping) {
/* Build value vector */
json_t *values;
if (cnt) {
values = json_array();
for (int k = 0; k < cnt; k++) {
struct msg *m = &pool[(first + k) % poolsize];
json_array_append_new(values, json_pack("[ f, f, i ]",
time_to_double(&MSG_TS(m)),
m->data[map->index].f,
m->sequence
));
}
}
else
values = json_string("");
/* Create Metadata for attribute */
json_t *metadatas = json_array();
json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: s+ }",
"name", "source",
"type", "string",
"value", "s2ss:", settings.name
));
json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: i }",
"name", "index",
"type", "integer",
"value", map->index
));
json_t *attribute = json_pack("{ s: s, s: s, s: o, s: o }",
"name", map->name,
"type", map->type,
"value", values,
"metadatas", metadatas
);
json_array_append_new(attributes, attribute);
}
return json_pack("{ s: s, s: s, s: b, s: o }",
"id", i->entity_id,
"type", i->entity_type,
"isPattern", 0,
"attributes", attributes
);
}
static int json_entity_parse(json_t *entity, struct ngsi *i, struct msg *pool, int poolsize, int first, int cnt)
{
int ret;
const char *id, *name, *type;
size_t index;
json_t *attribute, *attributes;
ret = json_unpack(entity, "{ s: s, s: s, s: o }",
"id", &id,
"type", &type,
"attributes", &attributes
);
if (ret || !json_is_array(attributes))
return -1;
if (strcmp(id, i->entity_id) || strcmp(type, i->entity_type))
return -2;
for (int j = 0; j < cnt; j++) {
struct msg *m = &pool[(first + j) % poolsize];
m->version = MSG_VERSION;
m->length = json_array_size(attributes);
m->endian = MSG_ENDIAN_HOST;
}
json_array_foreach(attributes, index, attribute) {
struct ngsi_mapping *map;
json_t *metadata, *values, *tuple;
/* Parse JSON */
ret = json_unpack(attribute, "{ s: s, s: s, s: o, s: o }",
"name", &name,
"type", &type,
"value", &values,
"metadatas", &metadata
);
if (ret)
return -3;
/* Check attribute name and type */
map = list_lookup(&i->mapping, name);
if (!map || strcmp(map->type, type))
return -4;
/* Check metadata */
if (!json_is_array(metadata))
return -5;
/* Check number of values */
if (!json_is_array(values) || json_array_size(values) != cnt)
return -6;
size_t index2;
json_array_foreach(values, index2, tuple) {
struct msg *m = &pool[(first + index2) % poolsize];
/* Check sample format */
if (!json_is_array(tuple) || json_array_size(tuple) != 3)
return -7;
char *end;
const char *value, *ts, *seq;
ret = json_unpack(tuple, "[ s, s, s ]", &ts, &value, &seq);
if (ret)
return -8;
m->sequence = atoi(seq);
struct timespec tss = time_from_double(strtod(ts, &end));
if (ts == end)
return -9;
m->ts.sec = tss.tv_sec;
m->ts.nsec = tss.tv_nsec;
m->data[map->index].f = strtof(value, &end);
if (value == end)
return -10;
}
}
return cnt;
}
struct ngsi_response {
char *data;
@ -86,26 +225,35 @@ static size_t ngsi_request_writer(void *contents, size_t size, size_t nmemb, voi
return realsize;
}
static int ngsi_request(CURL *handle, json_t *content, json_t **response)
static int ngsi_request(CURL *handle, const char *endpoint, const char *operation, json_t *request, json_t **response)
{
struct ngsi_response chunk = { 0 };
long code;
char *post = json_dumps(content, JSON_INDENT(4));
char *post = json_dumps(request, JSON_INDENT(4));
char url[128];
snprintf(url, sizeof(url), "%s/v1/%s", endpoint, operation);
curl_easy_setopt(handle, CURLOPT_URL, url);
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ngsi_request_writer);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void *) &chunk);
curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, strlen(post));
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, post);
debug(20, "Request to context broker:\n%s", post);
debug(18, "Request to context broker: %s\n%s", url, post);
int old; /* We don't want to leave the CUrl handle in an invalid state */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
CURLcode ret = curl_easy_perform(handle);
pthread_setcancelstate(old, NULL);
if (ret)
error("HTTP request failed: %s", curl_easy_strerror(ret));
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &code);
double time;
curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &time);
debug(20, "Response from context broker (code=%ld):\n%s", code, chunk.data);
debug(16, "Request to context broker completed in %.4f seconds", time);
debug(17, "Response from context broker:\n%s", chunk.data);
json_error_t err;
json_t *resp = json_loads(chunk.data, 0, &err);
@ -120,102 +268,7 @@ static int ngsi_request(CURL *handle, json_t *content, json_t **response)
free(post);
free(chunk.data);
return code;
}
void ngsi_prepare_context(struct node *n, config_setting_t *mapping)
{
struct ngsi *i = n->ngsi;
i->context = json_object();
i->context_len = config_setting_length(mapping);
i->context_map = alloc(i->context_len * sizeof(json_t *));
json_t *elements = json_array();
for (int j = 0; j < i->context_len; j++) {
/* Get token */
config_setting_t *ctoken = config_setting_get_elem(mapping, j);
const char *stoken = config_setting_get_string(ctoken);
if (!stoken)
cerror(mapping, "Invalid token");
/* Parse token */
char eid[64], etype[64], aname[64], atype[64];
if (sscanf(stoken, "%63[^().](%63[^().]).%63[^().](%63[^().])", eid, etype, aname, atype) != 4)
cerror(ctoken, "Invalid token: '%s'", stoken);
/* Create entity */
json_t *attributes; /* borrowed reference */
json_t *entity = json_lookup(elements, "id", eid);
if (!entity) {
entity = json_pack("{ s: s, s: s, s: b }",
"id", eid,
"type", etype,
"isPattern", 0
);
json_array_append_new(elements, entity);
attributes = json_array();
json_object_set_new(entity, "attributes", attributes);
}
else {
if (i->structure == NGSI_CHILDREN)
cerror(ctoken, "Duplicate mapping for index %u", j);
attributes = json_object_get(entity, "attributes");
}
/* Create attribute for entity */
if (json_lookup(attributes, "name", aname))
cerror(ctoken, "Duplicated attribute '%s' in NGSI mapping of node '%s'", aname, n->name);
/* Create Metadata for attribute */
json_t *metadatas = json_array();
json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: s }",
"name", "source",
"type", "string",
"value", "s2ss"
));
json_array_append_new(metadatas, json_pack("{ s: s, s: s, s: i }",
"name", "index",
"type", "integer",
"value", j
));
if (i->structure == NGSI_CHILDREN) {
json_array_append_new(attributes, json_pack("{ s: s, s: s, s: s }",
"name", "parentId",
"type", "uuid",
"value", eid
));
json_array_append_new(attributes, json_pack("{ s: s, s: s, s: s }",
"name", "source",
"type", "string",
"value", "measurement"
));
json_array_append_new(attributes, json_pack("{ s: s, s: s, s: o }",
"name", "timestamp",
"type", "date",
"value", json_date(NULL)
));
}
json_t *attribute = i->context_map[j] = json_pack("{ s: s, s: s, s: s }",
"name", aname,
"type", atype,
"value", "0"
);
json_object_set_new(attribute, "metadatas", metadatas);
json_array_append_new(attributes, attribute);
}
json_object_set_new(i->context, "contextElements", elements);
return 0;
}
int ngsi_init(int argc, char *argv[], struct settings *set)
@ -234,37 +287,49 @@ int ngsi_parse(config_setting_t *cfg, struct node *n)
{
struct ngsi *i = alloc(sizeof(struct ngsi));
if (!config_setting_lookup_string(cfg, "token", &i->token))
i->token = NULL; /* disabled by default */
if (!config_setting_lookup_string(cfg, "access_token", &i->access_token))
i->access_token = NULL; /* disabled by default */
if (!config_setting_lookup_string(cfg, "endpoint", &i->endpoint))
cerror(cfg, "Missing NGSI endpoint for node '%s'", n->name);
if (!config_setting_lookup_string(cfg, "entity_id", &i->entity_id))
cerror(cfg, "Missing NGSI entity ID for node '%s'", n->name);
if (!config_setting_lookup_string(cfg, "entity_type", &i->entity_type))
cerror(cfg, "Missing NGSI entity type for node '%s'", n->name);
if (!config_setting_lookup_bool(cfg, "ssl_verify", &i->ssl_verify))
i->ssl_verify = 1; /* verify by default */
if (!config_setting_lookup_float(cfg, "timeout", &i->timeout))
i->timeout = 1; /* default value */
const char *structure;
if (!config_setting_lookup_string(cfg, "structure", &structure))
i->structure = NGSI_FLAT;
else {
if (!strcmp(structure, "flat"))
i->structure = NGSI_FLAT;
else if (!strcmp(structure, "children"))
i->structure = NGSI_CHILDREN;
else
cerror(cfg, "Invalid NGSI entity structure '%s' for node '%s'", structure, n->name);
}
n->ngsi = i;
if (!config_setting_lookup_float(cfg, "rate", &i->rate))
i->rate = 5; /* default value */
config_setting_t *mapping = config_setting_get_member(cfg, "mapping");
if (!mapping || !config_setting_is_array(mapping))
cerror(cfg, "Missing mapping for node '%s", n->name);
ngsi_prepare_context(n, mapping);
list_init(&i->mapping, NULL);
for (int j = 0; j < config_setting_length(mapping); j++) {
const char *token = config_setting_get_string_elem(mapping, j);
if (!token)
cerror(mapping, "Invalid token in mapping for NGSI node '%s'", n->name);
struct ngsi_mapping map = {
.index = j
};
/* Parse token */
if (sscanf(token, "%m[^(](%m[^)])", &map.name, &map.type) != 2)
cerror(mapping, "Invalid mapping token: '%s'", token);
list_push(&i->mapping, memdup(&map, sizeof(struct ngsi_mapping)));
}
n->ngsi = i;
return 0;
}
@ -274,99 +339,138 @@ char * ngsi_print(struct node *n)
struct ngsi *i = n->ngsi;
char *buf = NULL;
return strcatf(&buf, "endpoint=%s, timeout=%.3f secs",
i->endpoint, i->timeout);
return strcatf(&buf, "endpoint=%s, timeout=%.3f secs, #mappings=%zu",
i->endpoint, i->timeout, list_length(&i->mapping));
}
int ngsi_open(struct node *n)
{
char buf[128];
struct ngsi *i = n->ngsi;
i->curl = curl_easy_init();
i->headers = NULL;
if (i->token) {
snprintf(buf, sizeof(buf), "Auth-Token: %s", i->token);
if (i->access_token) {
char buf[128];
snprintf(buf, sizeof(buf), "Auth-Token: %s", i->access_token);
i->headers = curl_slist_append(i->headers, buf);
}
/* Create timer */
i->tfd = timerfd_create(CLOCK_MONOTONIC, 0);
if (i->tfd < 0)
serror("Failed to create timer");
/* Arm the timer with a fixed rate */
struct itimerspec its = {
.it_interval = time_from_double(1 / i->rate),
.it_value = { 0, 1 },
};
if (i->timeout > 1 / i->rate)
warn("Timeout is to large for given rate: %f", i->rate);
int ret = timerfd_settime(i->tfd, 0, &its, NULL);
if (ret)
serror("Failed to start timer");
i->headers = curl_slist_append(i->headers, "User-Agent: S2SS " VERSION);
i->headers = curl_slist_append(i->headers, "Accept: application/json");
i->headers = curl_slist_append(i->headers, "Content-Type: application/json");
snprintf(buf, sizeof(buf), "%s/v1/updateContext", i->endpoint);
curl_easy_setopt(i->curl, CURLOPT_URL, buf);
curl_easy_setopt(i->curl, CURLOPT_SSL_VERIFYPEER, i->ssl_verify);
curl_easy_setopt(i->curl, CURLOPT_TIMEOUT_MS, i->timeout * 1e3);
curl_easy_setopt(i->curl, CURLOPT_HTTPHEADER, i->headers);
/* Create entity and atributes */
json_object_set_new(i->context, "updateAction", json_string("APPEND"));
return ngsi_request(i->curl, i->context, NULL) == 200 ? 0 : -1;
json_t *request = json_pack("{ s: s, s: [ o ] }",
"updateAction", "APPEND",
"contextElements", json_entity(i, NULL, 0, 0, 0)
);
return ngsi_request(i->curl, i->endpoint, "updateContext", request, NULL);
}
int ngsi_close(struct node *n)
{
struct ngsi *i = n->ngsi;
int ret;
/* Delete attributes */
json_object_set_new(i->context, "updateAction", json_string("DELETE"));
int code = ngsi_request(i->curl, i->context, NULL) == 200 ? 0 : -1;
/* Delete complete entity (not just attributes) */
json_t *request = json_pack("{ s: s, s: [ { s: s, s: s, s: b } ] }",
"updateAction", "DELETE",
"contextElements",
"type", i->entity_type,
"id", i->entity_id,
"isPattern", 0
);
ret = ngsi_request(i->curl, i->endpoint, "updateContext", request, NULL);
json_decref(request);
curl_easy_cleanup(i->curl);
curl_slist_free_all(i->headers);
return code == 200 ? 0 : -1;
return ret;
}
int ngsi_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt)
{
return -1; /** @todo not yet implemented */
{
struct ngsi *i = n->ngsi;
int ret;
const char *code;
timerfd_wait(i->tfd);
json_t *entity;
json_t *response;
json_t *request = json_pack("{ s: [ { s: s, s: s, s: b } ] }",
"entities",
"id", i->entity_id,
"type", i->entity_type,
"isPattern", 0
);
/* Send query to broker */
ret = ngsi_request(i->curl, i->endpoint, "queryContext", request, &response);
if (ret < 0) {
warn("Failed to query data from NGSI node '%s'", n->name);
return 0;
}
/* Parse response */
ret = json_unpack(response, "{ s: [ { s: o, s: { s: s } } ] }",
"contextResponses",
"contextElement", &entity,
"statusCode",
"code", &code
);
if (ret || strcmp(code, "200")) {
warn("Failed to parse response from NGSI node '%s'", n->name);
return 0;
}
ret = json_entity_parse(entity, i, pool, poolsize, first, cnt);
if (ret < 0) {
warn("Failed to parse entity from context broker response: reason=%d", ret);
return 0;
}
return ret;
}
int ngsi_write(struct node *n, struct msg *pool, int poolsize, int first, int cnt)
{
struct ngsi *i = n->ngsi;
struct msg *m = &pool[first % poolsize];
if (cnt > 1)
error("NGSI nodes only can send a single message at once");
/* Update context */
for (int j = 0; j < MIN(i->context_len, m->length); j++) {
json_t *attribute = i->context_map[j];
json_t *value = json_object_get(attribute, "value");
json_t *metadatas = json_object_get(attribute, "metadatas");
json_t *timestamp = json_lookup(metadatas, "name", "timestamp");
json_object_update(timestamp, json_pack("{ s: s, s: s, s: o }",
"name", "timestamp",
"type", "date",
"value", json_date(&MSG_TS(m))
));
char new[64];
snprintf(new, sizeof(new), "%f", m->data[j].f); /** @todo for now we only support floating point values */
json_string_set(value, new);
}
/* Update UUIDs for children structure */
if (i->structure == NGSI_CHILDREN) {
json_t *entity, *elements = json_object_get(i->context, "contextElements");
size_t ind;
json_array_foreach(elements, ind, entity)
json_object_set_new(entity, "id", json_uuid());
json_object_set_new(i->context, "updateAction", json_string("APPEND"));
}
else
json_object_set_new(i->context, "updateAction", json_string("UPDATE"));
json_t *response;
int code = ngsi_request(i->curl, i->context, &response);
if (code != 200)
json_t *request = json_pack("{ s: s, s : [ o ] }",
"updateAction", "UPDATE",
"contextElements", json_entity(i, pool, poolsize, first, cnt)
);
int ret = ngsi_request(i->curl, i->endpoint, "updateContext", request, &response); json_decref(request);
if (ret)
error("Failed to NGSI update Context request:\n%s", json_dumps(response, JSON_INDENT(4)));
return 1;

View file

@ -67,7 +67,9 @@ int nl_get_egress(struct nl_addr *addr)
{
int ret;
struct nl_sock *sock = nl_init();
struct nl_cb *cb;
struct nl_msg *msg = nlmsg_alloc_simple(RTM_GETROUTE, 0);
struct rtnl_route *route = NULL;
/* Build message */
struct rtmsg rmsg = {
@ -77,32 +79,37 @@ int nl_get_egress(struct nl_addr *addr)
ret = nlmsg_append(msg, &rmsg, sizeof(rmsg), NLMSG_ALIGNTO);
if (ret)
return ret;
goto out;
ret = nla_put_addr(msg, RTA_DST, addr);
if (ret)
return ret;
goto out;
/* Send message */
ret = nl_send_auto(sock, msg);
nlmsg_free(msg);
if (ret < 0)
return ret;
goto out;
/* Hook into receive chain */
struct rtnl_route *route = NULL;
struct nl_cb *cb = nl_cb_alloc(NL_CB_VALID);
cb = nl_cb_alloc(NL_CB_VALID);
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, egress_cb, &route);
/* Receive message */
nl_recvmsgs_report(sock, cb);
nl_wait_for_ack(sock);
nl_cb_put(cb);
/* Check result */
if (route && (1 <= rtnl_route_get_nnexthops(route))) {
struct rtnl_nexthop *nh = rtnl_route_nexthop_n(route, 0);
return rtnl_route_nh_get_ifindex(nh);
if (!route || rtnl_route_get_nnexthops(route) != 1) {
ret = -1;
goto out2;
}
else
return -1;
ret = rtnl_route_nh_get_ifindex(rtnl_route_nexthop_n(route, 0));
rtnl_route_put(route);
out2: nl_cb_put(cb);
out: nlmsg_free(msg);
return ret;
}

View file

@ -35,8 +35,7 @@ struct list node_types = LIST_INIT(NULL);
int node_init(int argc, char *argv[], struct settings *set)
{ INDENT
FOREACH(&node_types, it) {
const struct node_type *vt = it->type;
list_foreach(const struct node_type *vt, &node_types) {
if (vt->refcnt) {
info("Initializing '%s' node type", vt->name);
vt->init(argc, argv, set);
@ -49,13 +48,13 @@ int node_init(int argc, char *argv[], struct settings *set)
int node_deinit()
{ INDENT
/* De-initialize node types */
FOREACH(&node_types, it) {
struct node_type *vt = it->type;
list_foreach(const struct node_type *vt, &node_types) {
if (vt->refcnt) {
info("De-initializing '%s' node type", vt->name);
vt->deinit();
}
}
return 0;
}
@ -67,21 +66,25 @@ int node_start(struct node *n)
}
char *buf = node_print(n);
debug(1, "Starting node '%s' of type '%s' (%s)", n->name, n->vt->name, buf);
debug(1, "Starting node '%s' of type '%s' (%s)", n->name, n->_vt->name, buf);
free(buf);
{ INDENT
return n->vt->open(n);
return node_open(n);
}
}
int node_stop(struct node *n)
{ INDENT
int ret;
if (!n->refcnt) /* Unused and not started. No reason to stop.. */
return -1;
info("Stopping node '%s'", n->name);
{ INDENT
ret = n->vt->close(n);
ret = node_close(n);
}
return ret;
@ -89,31 +92,34 @@ int node_stop(struct node *n)
void node_reverse(struct node *n)
{
switch (n->vt->type) {
switch (node_type(n)) {
#ifdef ENABLE_SOCKET
case BSD_SOCKET:
SWAP(n->socket->remote, n->socket->local);
break;
#endif
case LOG_FILE:
SWAP(n->file->path_in, n->file->path_out);
SWAP(n->file->read, n->file->write);
break;
default: { }
}
}
struct node * node_create()
struct node * node_create(struct node_type *vt)
{
return alloc(sizeof(struct node));
struct node *n = alloc(sizeof(struct node));
n->_vt = vt;
return n;
}
void node_destroy(struct node *n)
{
switch (n->vt->type) {
switch (node_type(n)) {
#ifdef ENABLE_NGSI
case NGSI:
json_decref(n->ngsi->context);
free(n->ngsi->context_map);
list_destroy(&n->ngsi->mapping);
break;
#endif
#ifdef ENABLE_SOCKET
@ -122,10 +128,6 @@ void node_destroy(struct node *n)
rtnl_cls_put(n->socket->tc_classifier);
break;
#endif
case LOG_FILE:
free(n->file->path_in);
free(n->file->path_out);
break;
default: { }
}

View file

@ -14,6 +14,7 @@
#include "opal.h"
#include "utils.h"
/** Global OPAL specific settings */
static struct opal_global *og = NULL;
/** List of all opal nodes */
@ -122,9 +123,9 @@ int opal_print_global(struct opal_global *g)
free(rbuf);
debug(2, "Control Block Parameters:");
for (int i=0; i<GENASYNC_NB_FLOAT_PARAM; i++)
for (int i = 0; i < GENASYNC_NB_FLOAT_PARAM; i++)
debug(2, "FloatParam[]%u] = %f", i, og->params.FloatParam[i]);
for (int i=0; i<GENASYNC_NB_STRING_PARAM; i++)
for (int i = 0; i < GENASYNC_NB_STRING_PARAM; i++)
debug(2, "StringParam[%u] = %s", i, og->params.StringParam[i]);
return 0;
@ -134,12 +135,6 @@ int opal_parse(config_setting_t *cfg, struct node *n)
{
struct opal *o = alloc(sizeof(struct opal));
/* Checks */
if (n->combine != 1) {
config_setting_t *cfg_combine = config_setting_get_member(cfg, "combine");
cerror(cfg_combine, "The OPAL-RT node type does not support combining!");
}
config_setting_lookup_int(cfg, "send_id", &o->send_id);
config_setting_lookup_int(cfg, "recv_id", &o->recv_id);
config_setting_lookup_bool(cfg, "reply", &o->reply);
@ -174,7 +169,7 @@ int opal_open(struct node *n)
int sfound = 0, rfound = 0;
for (int i = 0; i < og->send_icons; i++)
sfound += og->send_ids[i] == o->send_id;
for (int i = 0; i<og->send_icons; i++)
for (int i = 0; i < og->send_icons; i++)
rfound += og->send_ids[i] == o->send_id;
if (!sfound)
@ -205,6 +200,9 @@ int opal_read(struct node *n, struct msg *pool, int poolsize, int first, int cnt
struct msg *m = &pool[first % poolsize];
double data[MSG_VALUES];
if (cnt != 1)
error("The OPAL-RT node type does not support combining!");
/* This call unblocks when the 'Data Ready' line of a send icon is asserted. */
do {
@ -266,6 +264,9 @@ int opal_write(struct node *n, struct msg *pool, int poolsize, int first, int cn
int state;
int len;
double data[m->length];
if (cnt != 1)
error("The OPAL-RT node type does not support combining!");
state = OpalGetAsyncModelState();
if ((state == STATE_RESET) || (state == STATE_STOP))

View file

@ -8,12 +8,12 @@
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include "utils.h"
#include "path.h"
#include "timing.h"
#include "config.h"
#include "stats.h"
#ifndef sigev_notify_thread_id
#define sigev_notify_thread_id _sigev_un._tid
@ -23,27 +23,34 @@ extern struct settings settings;
static void path_write(struct path *p)
{
FOREACH(&p->destinations, it) {
list_foreach(struct node *n, &p->destinations) {
int sent = node_write(
it->node, /* Destination node */
p->pool, /* Pool of received messages */
p->poolsize, /* Size of the pool */
p->received - it->node->combine,/* Index of the first message which should be sent */
it->node->combine /* Number of messages which should be sent */
n, /* Destination node */
p->pool, /* Pool of received messages */
p->poolsize, /* Size of the pool */
p->received - n->combine,/* Index of the first message which should be sent */
n->combine /* Number of messages which should be sent */
);
debug(1, "Sent %u messages to node '%s'", sent, it->node->name);
debug(15, "Sent %u messages to node '%s'", sent, n->name);
p->sent += sent;
clock_gettime(CLOCK_REALTIME, &p->ts_sent);
p->ts_sent = time_now(); /** @todo use hardware timestamps for socket node type */
}
}
int path_run_hook(struct path *p, enum hook_type t)
{
int ret = 0;
FOREACH(&p->hooks[t], it)
ret += ((hook_cb_t) it->ptr)(p);
list_foreach(struct hook *h, &p->hooks) {
if (h->type & t) {
ret = ((hook_cb_t) h->cb)(p, h, t);
debug(22, "Running hook when=%u '%s' prio=%u ret=%d", t, h->name, h->priority, ret);
if (ret)
return ret;
}
}
return ret;
}
@ -54,7 +61,14 @@ static void * path_run_async(void *arg)
struct path *p = arg;
/* Block until 1/p->rate seconds elapsed */
while (timerfd_wait(p->tfd)) {
for (;;) {
/* Check for overruns */
uint64_t expir = timerfd_wait(p->tfd);
if (expir > 1) {
p->overrun += expir;
warn("Overrun detected for path: overruns=%" PRIu64, expir);
}
if (path_run_hook(p, HOOK_ASYNC))
continue;
@ -75,14 +89,19 @@ static void * path_run(void *arg)
p->previous = p->current = p->pool;
/* Main thread loop */
for(;;) {
for (;;) {
/* Receive message */
int recv = node_read(p->in, p->pool, p->poolsize, p->received, p->in->combine);
if (recv < 0)
error("Failed to receive message from node '%s'", p->in->name);
else if (recv == 0)
continue;
/** @todo Replace this timestamp by hardware timestamping */
clock_gettime(CLOCK_REALTIME, &p->ts_recv);
debug(10, "Received %u messages from node '%s'", recv, p->in->name);
p->ts_last = p->ts_recv;
p->ts_recv = time_now();
debug(15, "Received %u messages from node '%s'", recv, p->in->name);
/* Run preprocessing hooks */
if (path_run_hook(p, HOOK_PRE)) {
@ -93,7 +112,7 @@ static void * path_run(void *arg)
/* For each received message... */
for (int i = 0; i < recv; i++) {
p->previous = p->current;
p->current = &p->pool[ p->received % p->poolsize];
p->current = &p->pool[p->received % p->poolsize];
p->received++;
@ -121,8 +140,14 @@ static void * path_run(void *arg)
int path_start(struct path *p)
{ INDENT
char *buf = path_print(p);
info("Starting path: %s (poolsize = %u)", buf, p->poolsize);
info("Starting path: %s (poolsize=%u, msgsize=%u, #hooks=%zu, rate=%.1f)",
buf, p->poolsize, p->msgsize, list_length(&p->hooks), p->rate);
free(buf);
/* We sort the hooks according to their priority before starting the path */
list_sort(&p->hooks, ({int cmp(const void *a, const void *b) {
return ((struct hook *) a)->priority - ((struct hook *) b)->priority;
}; &cmp; }));
if (path_run_hook(p, HOOK_PATH_START))
return -1;
@ -131,7 +156,7 @@ int path_start(struct path *p)
if (p->rate) {
struct itimerspec its = {
.it_interval = time_from_double(1 / p->rate),
.it_value = { 1, 0 }
.it_value = { 0, 1 }
};
p->tfd = timerfd_create(CLOCK_REALTIME, 0);
@ -173,69 +198,37 @@ char * path_print(struct path *p)
{
char *buf = alloc(32);
strcatf(&buf, "%s " MAG("=>"), p->in->name);
strcatf(&buf, "%s " MAG("=>") " [", p->in->name);
if (list_length(&p->destinations) > 1) {
strcatf(&buf, " [");
FOREACH(&p->destinations, it)
strcatf(&buf, " %s", it->node->name);
strcatf(&buf, " ]");
}
else
strcatf(&buf, " %s", p->out->name);
list_foreach(struct node *n, &p->destinations)
strcatf(&buf, " %s", n->name);
strcatf(&buf, " ]");
return buf;
}
int path_reset(struct path *p)
{
if (path_run_hook(p, HOOK_PATH_RESTART))
return -1;
p->sent =
p->received =
p->invalid =
p->skipped =
p->dropped = 0;
return 0;
}
struct path * path_create()
{
struct path *p = alloc(sizeof(struct path));
list_init(&p->destinations, NULL);
list_init(&p->hooks, free);
for (int i = 0; i < HOOK_MAX; i++)
list_init(&p->hooks[i], NULL);
#define hook_add(type, priority, cb) list_insert(&p->hooks[type], priority, cb)
hook_add(HOOK_MSG, 1, hook_verify);
hook_add(HOOK_MSG, 2, hook_restart);
hook_add(HOOK_MSG, 3, hook_drop);
hook_add(HOOK_MSG, 4, stats_collect);
hook_add(HOOK_PATH_START, 1, stats_start);
hook_add(HOOK_PATH_STOP, 2, stats_show);
hook_add(HOOK_PATH_STOP, 3, stats_stop);
hook_add(HOOK_PATH_RESTART, 1, stats_line);
hook_add(HOOK_PATH_RESTART, 3, stats_reset);
hook_add(HOOK_PERIODIC, 1, stats_line);
list_foreach(struct hook *h, &hooks) {
if (h->type & HOOK_INTERNAL)
list_push(&p->hooks, memdup(h, sizeof(*h)));
}
return p;
}
void path_destroy(struct path *p)
{
path_run_hook(p, HOOK_DEINIT);
list_destroy(&p->destinations);
for (int i = 0; i < HOOK_MAX; i++)
list_destroy(&p->hooks[i]);
list_destroy(&p->hooks);
free(p->pool);
free(p);

View file

@ -42,7 +42,7 @@ int main(int argc, char *argv[])
/* Setup timer */
struct itimerspec its = {
.it_interval = time_from_double(1 / rate),
.it_value = { 1, 0 }
.it_value = { 0, 1 }
};
int tfd = timerfd_create(CLOCK_REALTIME, 0);
@ -53,22 +53,20 @@ int main(int argc, char *argv[])
serror("Failed to start timer");
/* Print header */
fprintf(stderr, "# %-20s\t%s\t%s\n", "timestamp", "seqno", "data[]");
fprintf(stderr, "# %-20s\t\t%s\n", "sec.nsec(seq)", "data[]");
/* Block until 1/p->rate seconds elapsed */
while (limit-- > 0 || argc < 4) {
m.sequence += timerfd_wait(tfd);
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
struct timespec ts = time_now();
m.ts.sec = ts.tv_sec;
m.ts.nsec = ts.tv_nsec;
msg_random(&m);
msg_fprint(stdout, &m);
msg_fprint(stdout, &m, MSG_PRINT_ALL & ~MSG_PRINT_OFFSET, 0);
fflush(stdout);
m.sequence += timerfd_wait(tfd);
}
close(tfd);

View file

@ -62,7 +62,7 @@ int main(int argc, char *argv[])
{
int reverse = 0;
struct config_t config;
config_t config;
_mtid = pthread_self();
@ -89,6 +89,8 @@ int main(int argc, char *argv[])
sigaction(SIGINT, &sa_quit, NULL);
list_init(&nodes, (dtor_cb_t) node_destroy);
log_init();
config_init(&config);
config_parse(argv[optind], &config, &set, &nodes, NULL);
@ -100,7 +102,7 @@ int main(int argc, char *argv[])
node_reverse(node);
node->refcnt++;
node->vt->refcnt++;
node->_vt->refcnt++;
node_init(argc-optind, argv+optind, &set);
node_start(node);
@ -108,16 +110,22 @@ int main(int argc, char *argv[])
pool = alloc(sizeof(struct msg) * node->combine);
/* Print header */
fprintf(stderr, "# %-20s\t%s\t%s\n", "timestamp", "seqno", "data[]");
fprintf(stderr, "# %-20s\t\t%s\n", "sec.nsec+offset(seq)", "data[]");
for (;;) {
struct timespec ts = time_now();
int recv = node_read(node, pool, node->combine, 0, node->combine);
for (int i = 0; i < recv; i++) {
int ret = msg_verify(&pool[i]);
struct msg *m = &pool[i];
int ret = msg_verify(m);
if (ret)
warn("Failed to verify message: %d", ret);
/** @todo should we drop reordered / delayed packets here? */
msg_fprint(stdout, &pool[i]);
msg_fprint(stdout, &pool[i], MSG_PRINT_ALL, time_delta(&MSG_TS(m), &ts));
}
}

View file

@ -28,7 +28,7 @@ struct list nodes;
/** The global configuration */
struct settings settings;
static struct config_t config;
static config_t config;
static struct settings set;
static struct msg *pool;
static struct node *node;
@ -89,6 +89,8 @@ int main(int argc, char *argv[])
sigaction(SIGINT, &sa_quit, NULL);
list_init(&nodes, (dtor_cb_t) node_destroy);
log_init();
config_init(&config);
config_parse(argv[optind], &config, &set, &nodes, NULL);
@ -100,7 +102,7 @@ int main(int argc, char *argv[])
node_reverse(node);
node->refcnt++;
node->vt->refcnt++;
node->_vt->refcnt++;
info("Initialize node types");
node_init(argc-optind, argv+optind, &set);
@ -111,15 +113,24 @@ int main(int argc, char *argv[])
pool = alloc(sizeof(struct msg) * node->combine);
/* Print header */
fprintf(stderr, "# %-20s\t%s\t%s\n", "timestamp", "seqno", "data[]");
fprintf(stderr, "# %-20s\t\t%s\n", "sec.nsec+offset(seq)", "data[]");
for (;;) {
int i = 0;
while (i < node->combine) {
if (msg_fscan(stdin, &pool[i]) > 0)
msg_fprint(stdout, &pool[i++]);
else if (feof(stdin))
goto out;
for (int i = 0; i < node->combine; i++) {
struct msg *m = &pool[i];
int reason;
retry: reason = msg_fscan(stdin, m, NULL, NULL);
if (reason < 0) {
if (feof(stdin))
goto out;
else {
warn("Skipped invalid message message: reason=%d", reason);
goto retry;
}
}
else
msg_fprint(stdout, m, MSG_PRINT_ALL, 0);
}
node_write(node, pool, node->combine, 0, node->combine);

View file

@ -19,31 +19,26 @@
#include "cfg.h"
#include "path.h"
#include "node.h"
#include "stats.h"
#include "checks.h"
#ifdef ENABLE_OPAL_ASYNC
#include "opal.h"
#endif
/** Linked list of nodes */
struct list nodes;
/** Linked list of paths */
struct list paths;
/** The global configuration */
struct settings settings;
/** libconfig handle */
static config_t config;
struct list nodes; /**< Linked list of nodes */
struct list paths; /**< Linked list of paths */
struct settings settings; /**< The global configuration */
static config_t config; /**< libconfig handle */
static void quit()
{
info("Stopping paths");
FOREACH(&paths, it)
path_stop(it->path);
list_foreach(struct path *p, &paths)
path_stop(p);
info("Stopping nodes");
FOREACH(&nodes, it)
node_stop(it->node);
list_foreach(struct node *n, &nodes)
node_stop(n);
info("De-initializing node types");
node_deinit();
@ -53,13 +48,18 @@ static void quit()
list_destroy(&nodes);
config_destroy(&config);
info("Goodbye!");
info(GRN("Goodbye!"));
_exit(EXIT_SUCCESS);
}
static void realtime_init()
{ INDENT
if (check_kernel_cmdline())
warn("You should reserve some cores for the server (see 'isolcpus')");
if (check_kernel_rt())
warn("We recommend to use an PREEMPT_RT patched kernel!");
/* Use FIFO scheduler with real time priority */
if (settings.priority) {
struct sched_param param = {
@ -68,18 +68,20 @@ static void realtime_init()
if (sched_setscheduler(0, SCHED_FIFO, &param))
serror("Failed to set real time priority");
else
debug(3, "Set task priority to %u", settings.priority);
debug(3, "Set task priority to %u", settings.priority);
}
warn("Use setting 'priority' to enable real-time scheduling!");
/* Pin threads to CPUs by setting the affinity */
if (settings.affinity) {
cpu_set_t cset = to_cpu_set(settings.affinity);
if (sched_setaffinity(0, sizeof(cset), &cset))
serror("Failed to set CPU affinity to '%#x'", settings.affinity);
else
debug(3, "Set affinity to %#x", settings.affinity);
debug(3, "Set affinity to %#x", settings.affinity);
}
warn("Use setting 'affinity' to pin process to isolated CPU cores!");
}
/* Setup exit handler */
@ -143,14 +145,12 @@ int main(int argc, char *argv[])
char *configfile = (argc == 2) ? argv[1] : "opal-shmem.conf";
log_reset();
log_init();
info("This is Simulator2Simulator Server (S2SS)");
info (" Version: %s (built on %s, %s)", BLD(YEL(VERSION)),
BLD(MAG(__DATE__)), BLD(MAG(__TIME__)));
/* Checks system requirements*/
if (check_root())
error("The server requires superuser privileges!");
#ifdef LICENSE
if (check_license_trace())
error("This software should not be traced!");
@ -161,10 +161,6 @@ int main(int argc, char *argv[])
#endif
if (check_kernel_version())
error("Your kernel version is to old: required >= %u.%u", KERNEL_VERSION_MAJ, KERNEL_VERSION_MIN);
if (check_kernel_cmdline())
warn("You should reserve some cores for the server (see 'isolcpus')");
if (check_kernel_rtpreempt())
warn("We recommend to use an RT_PREEMPT patched kernel!");
/* Initialize lists */
list_init(&nodes, (dtor_cb_t) node_destroy);
@ -184,21 +180,21 @@ int main(int argc, char *argv[])
node_init(argc, argv, &settings);
info("Starting nodes");
FOREACH(&nodes, it)
node_start(it->node);
list_foreach(struct node *n, &nodes)
node_start(n);
info("Starting paths");
FOREACH(&paths, it)
path_start(it->path);
list_foreach(struct path *p, &paths)
path_start(p);
/* Run! */
if (settings.stats > 0) {
stats_header();
hook_stats_header();
for (;;) FOREACH(&paths, it) {
do list_foreach(struct path *p, &paths) {
usleep(settings.stats * 1e6);
path_run_hook(it->path, HOOK_PERIODIC);
}
path_run_hook(p, HOOK_PERIODIC);
} while (1);
}
else
pause();

View file

@ -30,6 +30,7 @@
#include "config.h"
#include "utils.h"
#include "socket.h"
#include "checks.h"
/** Linked list of interfaces */
extern struct list interfaces;
@ -39,12 +40,14 @@ static struct list sockets;
int socket_init(int argc, char * argv[], struct settings *set)
{ INDENT
if (check_root())
error("The 'socket' node-type requires superuser privileges!");
nl_init(); /* Fill link cache */
list_init(&interfaces, (dtor_cb_t) if_destroy);
/* Gather list of used network interfaces */
FOREACH(&sockets, it) {
struct socket *s = it->socket;
list_foreach(struct socket *s, &sockets) {
struct rtnl_link *link;
/* Determine outgoing interface */
@ -63,16 +66,16 @@ int socket_init(int argc, char * argv[], struct settings *set)
}
/** @todo Improve mapping of NIC IRQs per path */
FOREACH(&interfaces, it)
if_start(it->interface, set->affinity);
list_foreach(struct interface *i, &interfaces)
if_start(i, set->affinity);
return 0;
}
int socket_deinit()
{ INDENT
FOREACH(&interfaces, it)
if_stop(it->interface);
list_foreach(struct interface *i, &interfaces)
if_stop(i);
list_destroy(&interfaces);
@ -201,24 +204,20 @@ int socket_read(struct node *n, struct msg *pool, int poolsize, int first, int c
else if (bytes < 0)
serror("Failed recv");
debug(10, "Received packet of %u bytes: %u samples a %u values per sample", bytes, cnt, (bytes / cnt) / 4 - 4);
debug(17, "Received packet of %u bytes: %u samples a %u values per sample", bytes, cnt, (bytes / cnt) / 4 - 4);
for (int i = 0; i < cnt; i++) {
struct msg *m = &pool[(first+poolsize+i) % poolsize];
/* Convert headers to host byte order */
m->sequence = ntohl(m->sequence);
m->length = ntohs(m->length);
/* Convert message to host endianess */
if (m->endian != MSG_ENDIAN_HOST)
msg_swap(m);
/* Check integrity of packet */
if (bytes / cnt != MSG_LEN(m))
error("Invalid message len: %u for node '%s'", MSG_LEN(m), n->name);
bytes -= MSG_LEN(m);
/* Convert message to host endianess */
if (m->endian != MSG_ENDIAN_HOST)
msg_swap(m);
}
/* Check packet integrity */
@ -237,16 +236,13 @@ int socket_write(struct node *n, struct msg *pool, int poolsize, int first, int
struct iovec iov[cnt];
for (int i = 0; i < cnt; i++) {
struct msg *n = &pool[(first+i) % poolsize];
if (n->type == MSG_TYPE_EMPTY)
struct msg *m = &pool[(first+i) % poolsize];
if (m->type == MSG_TYPE_EMPTY)
continue;
/* Convert headers to network byte order */
n->sequence = htons(n->sequence);
iov[sent].iov_base = n;
iov[sent].iov_len = MSG_LEN(n);
iov[sent].iov_base = m;
iov[sent].iov_len = MSG_LEN(m);
sent++;
}
@ -270,7 +266,7 @@ int socket_write(struct node *n, struct msg *pool, int poolsize, int first, int
if (bytes < 0)
serror("Failed send");
debug(10, "Sent packet of %u bytes: %u samples a %u values per sample", bytes, cnt, (bytes / cnt) / 4 - 4);
debug(17, "Sent packet of %u bytes: %u samples a %u values per sample", bytes, cnt, (bytes / cnt) / 4 - 4);
return sent;
}

View file

@ -1,80 +0,0 @@
/** Hook functions to collect statistics
*
* @file
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2014-2015, Institute for Automation of Complex Power Systems, EONERC
* This file is part of S2SS. All Rights Reserved. Proprietary and confidential.
* Unauthorized copying of this file, via any medium is strictly prohibited.
*********************************************************************************/
#include "stats.h"
#include "path.h"
#include "timing.h"
#include "utils.h"
void stats_header()
{
info("%-32s : %-8s %-8s %-8s %-8s %-8s",
"Source " MAG("=>") " Destination", "#Sent", "#Recv", "#Drop", "#Skip", "#Invalid");
line();
}
int stats_line(struct path *p)
{
char *buf = path_print(p);
info("%-32s : %-8u %-8u %-8u %-8u %-8u", buf, p->sent, p->received, p->dropped, p->skipped, p->invalid);
free(buf);
return 0;
}
int stats_show(struct path *p)
{
if (p->hist_delay.total) { info("One-way delay:"); hist_print(&p->hist_delay); }
if (p->hist_gap.total) { info("Message gap time:"); hist_print(&p->hist_gap); }
if (p->hist_sequence.total) { info("Sequence number gaps:"); hist_print(&p->hist_sequence); }
return 0;
}
int stats_collect(struct path *p)
{
int dist = p->current->sequence - (int32_t) p->previous->sequence;
struct timespec ts1 = MSG_TS(p->current);
struct timespec ts2 = MSG_TS(p->previous);
hist_put(&p->hist_sequence, dist);
hist_put(&p->hist_delay, time_delta(&ts1, &p->ts_recv));
hist_put(&p->hist_gap, time_delta(&ts2, &ts1));
return 0;
}
int stats_start(struct path *p)
{
/** @todo Allow configurable bounds for histograms */
hist_create(&p->hist_sequence, -HIST_SEQ, +HIST_SEQ, 1);
hist_create(&p->hist_delay, 0, 2, 100e-3);
hist_create(&p->hist_gap, 0, 40e-3, 1e-3);
return 0;
}
int stats_stop(struct path *p)
{
hist_destroy(&p->hist_sequence);
hist_destroy(&p->hist_delay);
hist_destroy(&p->hist_gap);
return 0;
}
int stats_reset(struct path *p)
{
hist_reset(&p->hist_sequence);
hist_reset(&p->hist_delay);
hist_reset(&p->hist_gap);
return 0;
}

View file

@ -88,6 +88,7 @@ int main(int argc, char *argv[])
sigaction(SIGTERM, &sa_quit, NULL);
sigaction(SIGINT, &sa_quit, NULL);
log_init();
config_init(&config);
config_parse(argv[1], &config, &settings, &nodes, NULL);
@ -96,7 +97,7 @@ int main(int argc, char *argv[])
error("There's no node with the name '%s'", argv[3]);
node->refcnt++;
node->vt->refcnt++;
node->_vt->refcnt++;
node_init(argc-3, argv+3, &settings);
node_start(node);
@ -153,11 +154,8 @@ check:
void test_rtt() {
struct msg m = MSG_INIT(sizeof(struct timespec) / sizeof(float));
struct timespec ts;
struct timespec *ts1 = (struct timespec *) &m.data;
struct timespec *ts2 = alloc(sizeof(struct timespec));
struct timespec sent, recv;
double rtt;
struct hist hist;
hist_create(&hist, low, high, res);
@ -165,33 +163,30 @@ void test_rtt() {
fprintf(stdout, "%17s%5s%10s%10s%10s%10s%10s\n", "timestamp", "seq", "rtt", "min", "max", "mean", "stddev");
while (running && (count < 0 || count--)) {
clock_gettime(CLOCK_ID, ts1);
clock_gettime(CLOCK_ID, &sent);
m.ts.sec = sent.tv_sec;
m.ts.nsec = sent.tv_nsec;
node_write_single(node, &m); /* Ping */
node_read_single(node, &m); /* Pong */
clock_gettime(CLOCK_ID, ts2);
clock_gettime(CLOCK_ID, &recv);
rtt = time_delta(ts1, ts2);
double rtt = time_delta(&recv, &sent);
if (rtt < 0)
warn("Negative RTT: %f", rtt);
hist_put(&hist, rtt);
clock_gettime(CLOCK_REALTIME, &ts);
time_fprint(stdout, &ts);
m.sequence++;
fprintf(stdout, "%5u%10.3f%10.3f%10.3f%10.3f%10.3f\n", m.sequence,
fprintf(stdout, "%10lu.%06lu%5u%10.3f%10.3f%10.3f%10.3f%10.3f\n",
recv.tv_sec, recv.tv_nsec / 1000, m.sequence,
1e3 * rtt, 1e3 * hist.lowest, 1e3 * hist.highest,
1e3 * hist_mean(&hist), 1e3 * hist_stddev(&hist));
}
/* Housekeeping */
free(ts2);
hist_print(&hist);
struct stat st;
if (!fstat(fd, &st)) {
FILE *f = fdopen(fd, "w");
@ -200,5 +195,6 @@ void test_rtt() {
else
error("Invalid file descriptor: %u", fd);
hist_print(&hist);
hist_destroy(&hist);
}

View file

@ -31,6 +31,15 @@ uint64_t timerfd_wait_until(int fd, struct timespec *until)
return timerfd_wait(fd);
}
struct timespec time_now()
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return ts;
}
struct timespec time_add(struct timespec *start, struct timespec *end)
{
struct timespec sum = {
@ -81,14 +90,4 @@ double time_delta(struct timespec *start, struct timespec *end)
struct timespec diff = time_diff(start, end);
return time_to_double(&diff);
}
int time_fscan(FILE *f, struct timespec *ts)
{
return fscanf(f, "%lu.%lu", &ts->tv_sec, &ts->tv_nsec);
}
int time_fprint(FILE *f, struct timespec *ts)
{
return fprintf(f, "%lu.%09lu\t", ts->tv_sec, ts->tv_nsec);
}
}

View file

@ -31,14 +31,33 @@ int version_compare(struct version *a, struct version *b) {
return major ? major : minor;
}
int strftimespec(char *s, uint max, const char *format, struct timespec *ts)
int strftimespec(char *dst, size_t max, const char *fmt, struct timespec *ts)
{
struct tm t;
int s, d;
char fmt2[64], dst2[64];
if (localtime_r(&(ts->tv_sec), &t) == NULL)
return -1;
for (s=0, d=0; fmt[s] && d < 64;) {
char c = fmt2[d++] = fmt[s++];
if (c == '%') {
char n = fmt[s];
if (n == 'u') {
fmt2[d++] = '0';
fmt2[d++] = '3';
}
else
fmt2[d++] = '%';
}
}
/* Print sub-second part to format string */
snprintf(dst2, sizeof(dst2), fmt2, ts->tv_nsec / 1000000);
return strftime(s, max, format, &t);
/* Print timestamp */
struct tm tm;
if (localtime_r(&(ts->tv_sec), &tm) == NULL)
return -1;
return strftime(dst, max, dst2, &tm);
}
double box_muller(float m, float s)
@ -135,3 +154,12 @@ void * alloc(size_t bytes)
return p;
}
void * memdup(const void *src, size_t bytes)
{
void *dst = alloc(bytes);
memcpy(dst, src, bytes);
return dst;
}