6.5. Logging Messages in Links and Nodes
6.5.1. What this is about
This chapter describes methods to log and evaluate message data in LN. They are geared toward a complete ad-hoc recording of messages which can be used to analyze or debug a system.
6.5.2. Two methods: LN recorder and Daemon Logger
The two methods described here are the LN recorder and the ln daemon logger. Both allow to save message data in files, but do have technical differences.
6.5.2.1. Main differences
LN Recorder Features
The LN recorder is just an LN client which is started with a regexp pattern describing a set of topic or service names. When any of these topics sends messages, their content is stored as a time series. The same is true for service requests and responses. To do this, the LN recorder receives that data as an LN client, so it typically needs to be transmitted via a network.
Storage is done continuously in a efficient binary format along with metadata about the messages. In order to avoid stalls of the system or data loss, the location of storage needs to be on a local file system.
The data format is a special type of raw binary format which is called LNRDB, which can be easily read into memory-mapped numpy arrays, and equally easily converted into the HDF5 format which is widely used for time series and geophysical data. These data formats both support memory-mapping, which means that a computer can easily evaluate data sets which are larger than its RAM.
Because of the continuous storing operation, the LN recorder can store an almost unlimited duration and amount of data (as much as the local file system can store). However because data is transported as LN messages to the client node, there exist certain limits on the maximum bandwidth with which the data can be transmitted. These limits depend on both network hardware and connectivity.
LN Daemon Logger Features
The LN daemon logger runs in an LN daemon on a specific host. It
only records data from local topics, so that data can be stored at a very high
bandwidth. When doing the recording, the data is stored in a ring
buffer in-memory, which means that there is a maximum recording size
(and therefore, time) for the data to stay available, and if that
amount is surpassed, the oldest data is erased. The limit depends on
the hardware and RAM of the node. Recording is started and stopped by
LN client API calls, and a
further call instructs the manager to save the data to a file on
the manager node. The possible storage data formats are Matlab *.mat
files, which do have the practical limit that they should not be
larger than the physical RAM of the machine processing the data,
“ln-daemon-log-raw-data” files, “flat python pickle” or lnrecorder-pcap files.
The typical use case for recordings that use the LN daemon logger are captures of short segments of high-frequency data which is used for debugging in real-time contexts. Because no network transmission is necessary at the time of recording, the possible data rates are very high, which allows to store events and parameters, for example from hardware controllers, at a high frequency.
6.5.3. The LN Recorder
As already mentioned, the LN recorder is a client application which stores data from a set of topics and services as time series into a binary file.
This means that data can be transported across the network like any other message data.
6.5.3.1. Version Information
Both lnrecorder and the lnrdb Python library require
at least LN version 2.1.0. Prior versions have bugs which, among
other things, cause that metadata is not written correctly when
the lnrecorder is terminated by a signal. It works together
with older versions of LN.
See also
Note on Synchronization of logged Packages in the Frequently Asked Questions part.
6.5.3.2. Command Line Arguments
The lnrecorder is called with a required “command” argument and
options which indicate topic and service names. The command required to
record messages is “record”. This is followed by one or
more patterns for topics in the format "topic:<name-pattern>[@frequency]"
and for services in the format "service:<name-pattern>".
There, the name pattern is a regexp which matches the
patterns which should be recorded. A star "*" matches
all topics & services. The topics pattern can be followed by
a frequency option, which is formed of an "@" followed
by the frequency in Hertz. Using this option requires that
the publishers of the message set the message time stamps,
with a unit of seconds.
A further option is "-v" for “verbose”. In this mode,
the LN recorder prints out any topics which have
been added or removed during its run.
If new topics appear which match a pattern, they are included in the recording. Each topic recording is numbered with a global counter, this means when a topic is started and stopped multiple times, it is recorded with increasing counter values.
here the complete output of lnrecorder --help:
usage: lnrecorder [OPTIONS...] VERB [ARGS...]
VERBS can be one of:
record OUTPUT_FILE [name-pattern...]
start recording of matching topics and services. further arguments
are intrepreted as glob patterns. in this case only topics or services
with matching names are recorded. each pattern can be prefixed with 'topic:'
or 'service:' to only match topics or services.
prepend a '!' to exclude matching topics/patterns.
each topic pattern can be followed by a '@'-sign and a subscription rate
like this: 'large_topics*@3' for 3Hz subscription.
example: 'record topics:* !topics:do_not_log.*' (would record all topics that
do not start with 'do_not_log.')
replay INPUT_FILE [name-pattern...]
start replaying topics. further glob-patterns select
which topics to replay, if not all.
list INPUT_FILE
list contents of given file
idle
do nothing, just wait for LN-service requests
convert INPUT_FILE OUTPUT
convert from one logging format to another
write_msg_defs INPUT_FILE FOLDER
(unfinished, output topic msg defs to stdout)
OPTIONS can be:
-v produce more verbose output (repeat up to 3 times)
-s RELATIVE_START_TIMESTAMP
(only for replay) start replaying at given timestamp
this timestamp is relative to the logfile start-timestamp
-vs
show verbose service request/response events when they happen
-name NAME
use this NAME as prefix for all LN-services
-z --gzip
filter recorded log through gzip before writing to pcap
-j --bzip2
filter recorded log through bzip2 before writing to pcap
-J --xz
filter recorded log through xz before writing to pcap
6.5.3.3. Example
Here is an example lnrdb.lnc which can be found in the folder examples/guide/example_logging_lnrdb/:
1# this is a config for using not Cissy but a local install of
2# LN, using scons. It requires to install LN >= 2.1.0 !
3#
4# Important: The Python h5py package needs to be installed
5# manually, for example in Debian by issuing
6# "apt install python3-h5py"
7# (make sure here to match the Python version used when
8# installing LN!)
9
10instance
11name: lnrdb example for %(env USER) on %(hostname)
12manager: :%(get_port_from_string "%(instance_name)")
13enable_auto_groups: true
14
15add_message_definition_dir: %(CURDIR)/ln_msg_defs
16
17push_name_prefix: all
18
19process publisher
20pass_environment: PYTHONPATH, LD_LIBRARY_PATH
21change_directory: %(CURDIR)
22add flags: no_error_on_successful_stop
23node: localhost
24command: %(LN_PYTHON) publisher.py
25
26# record all LN topics at a rate of max 500 Hz!
27process lnrecorder
28node: localhost
29add flags: start_in_shell
30change_directory: %(CURDIR)/logs
31# lnrecorder -v record last "topic:*@500"
32command: [
33 FN=example_log_$(date +%Y%m%dT%H%M%S);
34 echo "recording to $FN";
35 (rm test_log 2>/dev/null; ln -s $FN test_log);
36 exec lnrecorder -v record $FN "topic:*@500"
37]
38
39process python example read log
40node: localhost
41change_directory: %(CURDIR)
42add flags: no_error_on_successful_stop
43pass_environment: PATH, PYTHONPATH, LD_LIBRARY_PATH
44add flags: use_execvpe
45command: %(LN_PYTHON) read_log.py
46
47process convert log
48node: localhost
49change_directory: %(CURDIR)
50pass_environment: PATH, PYTHONPATH, LD_LIBRARY_PATH
51add flags: no_error_on_successful_stop
52command: %(LN_PYTHON) convert_logs.py "test_log.hdf5" "logs/test_log"
53
54process python example read hdf5
55node: localhost
56# when running under cissy, this requires Python3
57# (can also run with h5py installed from pip)
58change_directory: %(CURDIR)
59pass_environment: PATH, PYTHONPATH, LD_LIBRARY_PATH
60add flags: no_error_on_successful_stop
61command: %(LN_PYTHON) read_hdf5.py "test_log.hdf5"
The message definitions used are as follows:
lnrdb_example/two_doubles
1double a
2double b
lnrdb_example/two_vectors
1double q[7]
2
3# matrix, row major, 6 rows, 7 columns:
4double J[42]
5
6# counts from 0 to 4:
7uint8_t flag
This example starts several processes. process “publisher” publishes some data, and the process “lnrecorder” records it.
An important point is that the lnrecorder needs to be started in a shell snippet with “exec”, or alternatively with the “use_execvpe” flag in the process section. This ensures that it will receive correct signals on termination, and this in turn makes sure that files with meta data are properly closed.
Note
todo: is this still necessary with LN >= 2.1.0?
6.5.3.4. Synchronization of logged Data and Time Stamps
The lnrecorder does not ensure that data from different topic subscribers or processes are logged in a synchronized manner. It solely records any packets that were captured during its time of operation. When different subscribers, or different processes are started, they will not start operation at the same time (the difference can easily be 0.5 seconds for the subscribe operation alone). Therefore, both the original times of recorded messages, and the number of messages are expected to differ.
However, the lnrecorder records the time stamps which are passed when any packets are sent. These refer to the system clock, and allow for a precise synchronization of different messages, and aligning related data. If recording from different hosts need to be related in terms of time, it must be ensured that their system clocks are synchronized via network time protocol (NTP) or other means.
Each lnrdb table for a recorded topic includes these two timestamp columns:
timestamplocal system time when lnrecorder got hold of this topic message. It corresponds to the_packet_log_tsin ln_daemon recordings.publisher_timestampsystem time on publisher host when message was published. It corresponds to the_packet_source_tsin ln_daemon recordings.
Note
the timestamp is usually of less importance: it will show more jitter, drift and probably lags behind by
the time needed for transfer between hosts…
See also
Note on Synchronization of logged Packages in the Frequently Asked Questions part.
Description of time stamp for the ln_read() function in
Receiving Packets.
6.5.3.5. LN Service Interface aka idle-mode
when lnrecorder is started like this: lnrecorder idle, it will only react to these
LN-service calls:
<name>.recordusing mdlnrecorder/record<name>.record_waitingusing mdlnrecorder/record_waiting<name>.stopusing mdlnrecorder/stop<name>.replayusing mdlnrecorder/replay
<name> defaults to lnrecorder but if started via ln-manager it will be the name
fo the process definition which started the lnrecorder.
when calling the .record-service you need to provide these fields:
filenamename of to be created lnrdb or.pcap-filepatternsis a list ofln/string. here you can specify exactly the same topic- / service-name-patterns as you would do on the command line (including the@-rate-limiter for topics). if this list is kept empty, all topics & services will be logged!
a service response with empty error_message field indicates that the lnrecorder is now recording.
The .record service waits up to 10 seconds until topics that existed when recording started have
received a first packet, but it still reports success if that maximum wait time expires.
The .record_waiting service uses the same fields as .record and adds:
wait_timeoutmaximum time in seconds to wait until initially existing topics have received packets.
The .record_waiting response includes pending_topics, a list of initially existing topics that
had not received packets when wait_timeout expired. The recording has already started; callers can
decide whether a non-empty pending_topics list is an error for their use case.
to stop a recording, you should call the .stop LN-service. it does not need any request fields.
after the lnrecorder is again in its stopped-state, you can either start another recording or start a replay:
to replay a log you call the .replay LN-service and provide these request fields:
filenameis the name of the lnrdb or.pcap-filepatternsis a list ofln/stringand is optional. use it to only replay a selection of the recorded topics / services. if not specified, all topics / services are replayed.start_timestart replaying from a given time-offset (seconds) into the recording. use0to start from the beginning.stop_timestop playback at given time in seconds after recording-start. set to0to play until end of recording.waitoptional. if set to1the service call will wait until the replay is finished. otherwise the service call will return as soon as the replay was started
a replay can also be stopped via the .stop LN-service from above.
6.5.3.6. Data Accesss
6.5.3.6.1. Reading logged Data in LNRDB Format into Python
The process “python example read log” reads the generated log data, and prints some values. It uses the “lnrdb” python module from the links_and_nodes_python package, ands its listing looks like this:
1import lnrdb
2
3log = lnrdb.Db("logs/test_log")
4
5print("logged tables / topic instances:")
6for tn in log.tables.keys():
7 print(" - %r %d rows" % (tn, log.tables[tn].n_rows))
8
9print("first & last row::")
10for tn in log.tables.keys():
11 print(" - %r" % tn)
12 table = log.tables[tn]
13 print(" original topic name: %r" % table.meta["topic.name"])
14 print(" original topic md: %r" % table.meta["topic.md"])
15 print(" numpy dtype: %r" % (table.dtype, ))
16 if table.n_rows > 0:
17
18 data_mmap = table.mmap() # this numpy mmap object handles mostly like any other numpy recarray!
19
20 print(" first row: %r" % (data_mmap[0], ))
21 print(" last row: %r" % (data_mmap[-1], ))
22 if table.meta["topic.md"] == "lnrdb_example/two_vectors":
23 i = int(table.n_rows / 2)
24 last = data_mmap[i]
25 print(" middle idx: %r" % (i, ))
26 print(" middle q: %r" % (last.q, ))
27 N = last.q.shape[0]
28 print(" middle J: %r" % (last.J.reshape(-1, N), ))
29 print(" middle flag: %r" % (last.flag, ))
30
An important aspect of the lnrdb library is its capability to return memory-mapped numpy arrays. This means that it is possible to access time series which are far larger than a size that would fit into memory, but in the same way as a “normal” memory-backed numpy array. Data is read from disk only on-demand and can also be modified.
6.5.3.6.2. Converting Logged Data to HDF5
The process “convert log” reads the LNRDB binary data and saves it in HDF5 format. HDF5 is a scientific data format that was developed for time series of large amounts of geophysical data, for example. One advantage it has is that it can hold any relevant metadata, and also that it can be easily converted into other formats.
The converter script shown here takes at least two arguments: The first is the hdf5 file which is to be generated as output, and the following ones are the lnrdb recordings which should be included in the hdf5 file. That file acts like a kind of archive and container and can hold multiple time series as well as their meta data.
1import sys
2import lnrdb
3import h5py
4
5
6def convert_recording(f, recording_name):
7 grp = f.create_group(recording_name)
8
9 log = lnrdb.Db(recording_name)
10
11 print("converting logged tables / topic instances:")
12
13 for tn in log.tables.keys():
14 vars_log = vars(log.tables[tn])
15 # print(vars(log.tables[tn]))
16 print("vars_log.keys() ", vars_log.keys())
17 if "rows" in vars_log.keys():
18 print("found rows")
19 print(" - %r %d rows" % (tn, log.tables[tn].n_rows))
20
21 table = log.tables[tn]
22 print(" original topic name: %r" % table.meta["topic.name"])
23 print(" original topic md: %r" % table.meta["topic.md"])
24 print(" numpy dtype: %r" % (table.dtype,))
25
26 if table.n_rows > 0:
27
28 data_mmap = table.mmap() # this numpy mmap object handles mostly like
29 # any other numpy recarray!
30 dset = grp.create_dataset(tn, data=data_mmap, dtype=table.dtype)
31 else:
32 dset = grp.create_dataset(tn, (0,), dtype=table.dtype)
33
34 # keep all meta information
35 for meta_name, meta_value in table.meta.items():
36 dset.attrs[meta_name] = meta_value
37
38
39if len(sys.argv) < 3:
40 print(
41 """Usage: convert_logs <hdf5_filename> <recording1> [<recording2> ...]
42 converts one or more lnrecorder recordings in lnrdb format into one HDF5 file,
43 conserving meta data"""
44 )
45
46print("converting lnrecorder logs (using h5py from %s)" % h5py.__file__)
47print("converting lnrecorder logs (using lnrdb from %s)" % lnrdb.__file__)
48
49
50with h5py.File(sys.argv[1], "w") as f:
51 for recording_name in sys.argv[2:]:
52 convert_recording(f, recording_name)
53
54print("conversion finished.")
Once the conversion is done, no dependencies on LN or the real-time run-time system are needed, which means that the data can be analysed off-line if desired.
there is also a ready to use conan-packaged stand-alone converter: lnrdb_to_hdf5_converter/1.0.0@common/unstable
for details check here https://rmc-github.robotic.dlr.de/common/lnrdb_to_hdf5_converter
6.5.3.6.3. Reading HDF5 Data
As with the lnrdb module, such HDF5 files can be used to access memory-mapped binary data. The script “read_hdf5.py” shows how this can be done:
1
2from __future__ import print_function, division
3
4import sys
5import h5py
6import numpy as np
7
8list_of_datasets = []
9def printshape(name):
10 try:
11 print(name, f[name].shape)
12 list_of_datasets.append(name)
13 # note that in LNRDB, the final part of the dataset name
14 # (after the topic) is a process-global index that numbers the
15 # order in which the topic was started to be
16 # recorded. Multiple runs of a publisher generate multiple
17 # datasets (and the order in which topic recording starta can
18 # change if topics start in parallel!).
19 except AttributeError:
20 print(name, "has no shape")
21
22def show_extract(f, dataset_name):
23 dset = f[dataset_name]
24
25 print("+" * 20, "\n\n\n")
26 print("dataset %s" % dataset_name)
27 print(" shape = %r" % dset.shape)
28
29 if dset.shape[0] > 0:
30 print(" initial time stamp = {:>10.3f}, publisher_timestamp = {:>10.3f}, "
31 "timestamp delta = {:>5.3f}".format(dset[0, "timestamp"],
32 dset[0, "publisher_timestamp"],
33 dset[0, "publisher_timestamp"] - dset[0, "timestamp"]))
34 print(" final time stamp = {}, publisher_timestamp = {}, "
35 "timestamp delta = {:>5.3f}".format(dset[-1, "timestamp"],
36 dset[-1, "publisher_timestamp"],
37 dset[-1, "publisher_timestamp"] - dset[-1, "timestamp"]))
38
39 print(" first record:", dset[0])
40
41 n_rec = dset.shape[0]
42 middle_idx = n_rec // 2
43 for idx in [0, middle_idx, -1]:
44 for col in dset.dtype.names :
45 print(" {}({}) = {}".format(col, idx, dset[idx,col]))
46
47
48with h5py.File(sys.argv[1], "r") as f:
49 f.visit(printshape)
50
51 for ds in list_of_datasets:
52 show_extract(f, ds)
53
6.5.3.6.4. Reading HDF5 into Matlab
The script in matlab/example_hdf5.m shows how a HDF5
file can be accessed in matlab.
1%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2
3% first topic
4h5disp('test_log.hdf5', '/logs/test_log/topics/topic0/1')
5
6%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
7% second topic
8
9h5disp('test_log.hdf5', '/logs/test_log/topics/topic1/2')
10data2 = h5read('test_log.hdf5', '/logs/test_log/topics/topic1/2',1,1)
11
12timestamp_start = data2.timestamp(1)
13publisher_timestamp_start = data2.publisher_timestamp(1)
14a_start = data2.a(1)
15b_start = data2.b(1)
16
17%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
18%% third topic
19
20%% shows how to read time series data from a large file that
21%% dnoes not fit into memory
22%% key is to do the indexing in h5read so that matlab
23%% does not load the whole data set into memory
24
25h5disp('test_log.hdf5', '/logs/test_log/topics/topic2/3')
26
27% read first record
28data3_s = h5read('test_log.hdf5', '/logs/test_log/topics/topic2/3',1,1)
29
30q_start = data3_s.q
31J_start = data3_s.J
32
33% read last record
34data3_info = h5info('test_log.hdf5', '/logs/test_log/topics/topic2/3')
35
36data3_len = data3_info.Dataspace.Size
37
38data3_e = h5read('test_log.hdf5', '/logs/test_log/topics/topic2/3',data3_len,1)
39
40
41q_end = data3_e.q
42J_end = data3_e.J
43
44% read record in the middle
45
46% remember that umpy uses zero-based indices, while Matlab uses
47% indices starting from one
48mid_idx = floor(data3_len/2) + 1
49
50data3_mid = h5read('test_log.hdf5', '/logs/test_log/topics/topic2/3',mid_idx,1)
51
52q_mid = data3_mid.q
53
54J_mid = reshape(data3_mid.J, [6,7])
55flag_mid = data3_mid.flag
Here, the hdf5 reader routine returns matlab matrix objects. In difference to Numpy, these objects are not memory-mapped. This means that if the time series is very large, only a selection of it can be read into memory at the same time (Matlab is capable to access memory-mapped data, but only with low-level routines).
6.5.3.6.5. Direct Access to LNRDB Data in Matlab
As a last example, we show some scripts which read binary LNRDB data directly in matlab:
1%% first directly read all of first topic1 recording
2data = read_topic1('logs/test_log/topics_topic1_2/data');
3% this only works because read_topic1 knows the data format!
4% -> lnrdb_example/two_doubles
5whos data
6l = length(data)
7log_ts = data(:, 1);
8pub_ts = data(:, 2);
9
10disp(sprintf('first row [%f, %f]', data(1, 3), data(1, 4)));
11disp(sprintf('last row [%f, %f]', data(l, 3), data(l, 4)));
12
13%% directly read all of first topic2 recording
14[log_ts, pub_ts, q, J, flag] = read_topic2('logs/test_log/topics_topic2_3/data');
15% this only works because read_topic2 knows the data format!
16% -> lnrdb_example/two_vectors
17whos q J
18middle = floor(length(q) / 2) + 1
19disp(['middle q: ', sprintf('%f ', q(middle, :))]);
20
21middle_J = reshape(J(middle, :, :), 6, 7)
22middle_J_size = size(middle_J)
23
24middle_flag = flag(middle)
1function data = read_topic1(fn)
2% reads lnrdb data file from topics of md `lnrdb_example/two_doubles`
3
4% there are always two timestamp-doubles per row!
5% first is logger-timestamp,
6% second is publisher-timestmap
7
8fid = fopen(fn);
9data = fread(fid, [2 + 2, Inf], 'double')';
10fclose(fid);
1function [log_ts, pub_ts, q, J, flag] = read_topic2(fn)
2% reads lnrdb data file from topics of md `lnrdb_example/two_vectors`
3
4% there are always two timestamp-doubles per row!
5% first is logger-timestamp,
6% second is publisher-timestmap
7
8% first read & interpret all double values
9fid = fopen(fn);
10n_doubles = 2 + 7 + 42 % 51
11data = fread(fid, [n_doubles, Inf], '51*double', 1)'; % skip the flag byte at the end of each row
12fclose(fid);
13
14[l, cols] = size(data);
15
16log_ts = data(:, 1);
17pub_ts = data(:, 2);
18
19data = data(:, 3:cols);
20[l_, cols] = size(data);
21
22q = data(:, 1:7);
23
24J = data(:, 1+7:cols);
25% restore matrices, and transpose last two dims
26J = reshape(J, length(J), 7, 6);
27J = permute(J, [1, 3, 2]);
28
29% now read uint8-flag
30fid = fopen(fn);
31fseek(fid, n_doubles * 8, 'bof'); % skip first n_doubles in first row
32flag = fread(fid, [Inf], 'uint8', n_doubles * 8)'; % after each flag skip n_doubles of next row
33fclose(fid);
The offsets need to be computed from the message definitions.
As we see, this approach is more fragile, since it needs to use manually computed direct offsets into the binary files, which depend on the message definitions. Any change to the message definitions will render these offsets invalid, requiring consistent updates to avoid reading invalid binary data.
6.5.3.6.6. Troubleshooting
If any metadata files appear to be missing, check that the right LN version >= 2.1.0 is used, and that the lnrecorder program is started either with the “exec” shell command, or with the “execvpe” flag in the LNM configuration for the process!
6.5.4. The LN Daemon Logger
The LN daemon logger can be accessed by the LN client API, which allows to instruct an LN daemon to log message data to an circular buffer in RAM. It is run from an LN client, and instructs the daemon on a given node to log named topics.
Here is an example script how to use it:
1#!/usr/bin/python
2# -*- mode: python -*-
3
4import sys
5import time
6import links_and_nodes as ln
7
8from numpy import *
9
10logfile = "/tmp/log_test.topics.pcap"
11
12if __name__ == "__main__":
13 if len(sys.argv) != 3:
14 print("Please specify logfile and command as arguments to script!")
15 print("Usage:")
16 print(" %s <pickle logfile> <command>" % sys.argv[0])
17 sys.exit(1)
18
19 clnt = ln.client("ln_daemon log test", sys.argv)
20 logger = clnt.get_logger("test_logger")
21
22 logfile = sys.argv[1]
23 command = sys.argv[2]
24
25 if command == "start":
26 logger.add_topic("test.topic1", 100000)
27 logger.add_topic("test.topic2", 100000)
28 logger.enable()
29 print("Started logging...")
30
31 elif command == "stop":
32 print("Stop logging...")
33 logger.disable()
34
35 print("Saving logfile as %s at manager" % logfile)
36 logger.manager_save(logfile)
37
38 else:
39 raise Exception(
40 "Command '%s' not known! Only commands supported: start, stop" % command
41 )
Here, logger.add_topic() configures logging for two topics: test.topic1
and test.topic2, for each topic the last 100000 samples will be kept in
RAM. logger.enable() starts logging, and logger.disable() stops
it. logger.manager_save(filename) instructs the LN manager to fetch the
collected data from the daemons and save it to the given file.
The default is to log the “source shared memory” on the host the publisher runs
on. Each shared-memory can only have one logger running at a time. If you have a subscriber on a different host or a subscriber with
subscriber-rate != -1 they will get their own shared-memory. If you want to
log data that arrives there, you need to specify a topic-name in the form of
.add_topic("subscriber:<CLIENT-NAME>[@<SUB-RATE>]:<TOPIC-NAME>) where
<CLIENT-NAME> is the unique name of the ln-client doing that subscription
(check the ln_manager’s “clients”-tab). specifying a <SUB-RATE> is only
useful if that client does multiple subscriptions of the same topic (not many
use-cases).
In the example, the file name terminaes with “pcap”, so that PCAP format is
used. The logger can also save in Matlab’s .mat format or python’s
.pickle.
At the time being, recording of complex data structures is probably done best with the LN recorder with its “lnrdb” format that can be converted into HDF5 if needed.
6.5.5. LN Recorder and Daemon Logger: When to use Which One
aspect |
LN recorder |
Daemon logger |
|---|---|---|
max duration |
huge; limited by file system |
limited by ram of publisher-node |
max data rate |
limited by network |
very fast; limited by shared memory |
suitable for complex data |
yes |
less |
stores meta data |
yes |
no |
supported file formats |
LNRDB, HDF5, PCAP |
PICKLE, PCAP, MAT, RAW |
memory-mapped view avilable |
yes |
no (could be done with RAW format) |
typical usage |
recording motions, I/O, workflow |
debugging hardware drivers, controllers |