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:
commit
affb5c6eb3
73 changed files with 2091 additions and 1364 deletions
|
@ -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
|
||||
|
|
BIN
clients/labview/Data_to_string.vi
Normal file
BIN
clients/labview/Data_to_string.vi
Normal file
Binary file not shown.
BIN
clients/labview/I16_to_string.vi
Normal file
BIN
clients/labview/I16_to_string.vi
Normal file
Binary file not shown.
BIN
clients/labview/I32_to_string.vi
Normal file
BIN
clients/labview/I32_to_string.vi
Normal file
Binary file not shown.
BIN
clients/labview/MSG_create.vi
Normal file
BIN
clients/labview/MSG_create.vi
Normal file
Binary file not shown.
BIN
clients/labview/MSG_interpret.vi
Normal file
BIN
clients/labview/MSG_interpret.vi
Normal file
Binary file not shown.
BIN
clients/labview/SGL_to_string.vi
Normal file
BIN
clients/labview/SGL_to_string.vi
Normal file
Binary file not shown.
BIN
clients/labview/Simple.UDP_v1.vi
Normal file
BIN
clients/labview/Simple.UDP_v1.vi
Normal file
Binary file not shown.
BIN
clients/labview/UNIX_Time_Simple.vi
Normal file
BIN
clients/labview/UNIX_Time_Simple.vi
Normal file
Binary file not shown.
BIN
clients/labview/UNIX_Time_string.vi
Normal file
BIN
clients/labview/UNIX_Time_string.vi
Normal file
Binary file not shown.
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) */
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -37,6 +37,31 @@ Development tab -> Compiler -> Compiler Command (makefile) add the following com
|
|||
max umber of values in UDP packets:
|
||||
there’s 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
|
||||
|
||||
---------------------------------------------------
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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: ???
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'.
|
||||
]
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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_ @} @} */
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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) */
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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_ */
|
|
@ -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_ */
|
|
@ -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)) \
|
||||
|
|
190
server/src/cfg.c
190
server/src/cfg.c
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ int check_kernel_version()
|
|||
return version_compare(¤t, &required) < 0;
|
||||
}
|
||||
|
||||
int check_kernel_rtpreempt()
|
||||
int check_kernel_rt()
|
||||
{
|
||||
return access(SYSFS_PATH "/kernel/realtime", R_OK);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
131
server/src/msg.c
131
server/src/msg.c
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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: { }
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, ¶m))
|
||||
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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Add table
Reference in a new issue