4.6. Tutorials¶
4.6.1. Example: Getting Started with Replay and Shapes Demo¶
Start by recording Square data, as described in Section 3.6.1.
4.6.1.1. Start Shapes Demo and Subscribe to Squares¶
If you have any Shapes Demo windows publishing data, delete them.
Start Shapes Demo from Launcher and create a Square subscriber as described in Section 3.6.1.
4.6.1.2. Start Replay Service¶
Start Replay Service:
<NDDSHOME>/bin/rtireplayservice -cfgName UserReplayServiceJson -verbosity 3
You should see Square data replayed in Shapes Demo.
4.6.2. Example: Replaying Data at a Different Rate¶
To replay data at a faster or slower rate than it was recorded, you can edit the configuration file to specify a playback rate.
Start by recording Square data as described in Section 3.6.1.
4.6.2.1. Edit the Replay Configuration¶
In your <RTI_WORKSPACE>/user_config/recording_service
directory, edit the
USER_REPLAY_SERVICE.xml
file. Add a playback rate, as seen in the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<!-- All of our files start with a dds tag -->
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../resource/schema/rti_replay_service.xsd">
<replay_service name="UserReplayServiceJson">
<!-- This will look for files in the directory 'json_recording' in the
current working directory. This is integrated with the Recording
Service configuration 'UserRecorderServiceJson' in the file
USER_RECORDING_SERVICE.xml -->
<storage>
<sqlite>
<storage_format>JSON_SQLITE</storage_format>
<database_dir>json_recording</database_dir>
</sqlite>
</storage>
<!-- Optionally select the begin and end times for the data to be
replayed -->
<!--data_selection>
<time_range>
<begin_time>
<sec>0</sec>
<nanosec>0</nanosec>
</begin_time>
</time_range>
</data_selection-->
<!-- Specify playback behavior, including what local time to start -->
<playback>
<rate>2</rate>
</playback>
<domain_participant name="DefaultParticipant">
<domain_id>0</domain_id>
</domain_participant>
<session name="DefaultSession"
default_participant_ref="DefaultParticipant">
<!-- Topics to replay in this session -->
<topic_group name="DefaultTopicGroup">
<!-- Topics to replay -->
<allow_topic_name_filter>*</allow_topic_name_filter>
<deny_topic_name_filter>rti/*</deny_topic_name_filter>
</topic_group>
</session>
</replay_service>
</dds>
4.6.2.2. Start Shapes Demo¶
Use Launcher to start Shapes Demo, then create a Square subscriber.
4.6.2.3. Start Replay Service¶
Start Replay Service:
<NDDSHOME>/bin/rtireplayservice -cfgName UserReplayService -verbosity 3
You should see Square data replayed in Shapes Demo twice as fast as it was originally recorded.
4.6.3. Example: Plugging in Custom Storage¶
If you created a storage plugin to record data into your own custom storage, you can also create a plugin to replay from that same storage.
There are full examples written in C and C++ about plugging in custom storage in Recording Service, in the RTI Community Recording Service examples: C storage plugin and RTI Community Recording Service examples: C++ storage plugin.
4.6.3.1. Custom Storage API Overview¶
To retrieve data for replay, you must implement the following APIs:
- A create storage reader API:
This is used to create a StorageReader object.
This API is a C function. In C++, you can use macros to declare and define the C function for your class. For example:
RTI_RECORDING_STORAGE_READER_CREATE_DECL(FileStorageReader)
RTI_RECORDING_STORAGE_READER_CREATE_DEF(FileStorageReader)
The StorageReader is used to create and delete StorageStreamInfoReaders and StorageStreamReaders.
- StorageReader:
A create stream info reader API, where you create a stream reader that provides information about what streams (topics) are in your storage.
A delete stream info reader API, where you delete a stream info reader
A create stream reader API, where you create a stream reader
A delete stream reader API, where you delete a stream reader
- StorageStreamInfoReader:
Read: An API to retrieve discovery data from the storage. It uses a selector object to determine the kind of samples (not read or any kind) to be returned, as well as the time range. The samples should be returned in increasing time-stamp order.
Return loan: An API that notifies the plugin that it can release resources associated with the data passed to the read API
Get service start time: An API to query the recorded time from which to start replaying
Get service stop time: An API to query the recorded time at which to stop replaying
Finished: An API to tell Replay Service and Converter that there are no more stream infos to read.
Reset: An API called by the Replay Service to tell the plug-in to reset its state because it is looping. After this method is called, the stream reader should be ready to start reading data from the beginning of the stream, again.
- StorageStreamReader
Read: An API to retreive data from storage. It uses a selector object to determine the kind of samples (not read or any kind) to be returned, as well as the time range. The samples should be returned in increasing timestamp order.
Return loan: An API that notifies the plugin that it can release resources associated with the data passed to the take API
Finished: An API that notifies the plugin that there is no more data in this data stream
The APIs provide a mechanism to have strongly typed StorageStreamReader classes.
A builtin one is provided, based on dds::core::xtypes::DynamicData
.
More detailed API documentation is in:
4.6.4. Using Timestamp Tags with Replay Service¶
If your recording was originally made with the builtin SQLite storage plugin,
and you used the tag_timestamp
remote command to tag certain events,
then your recording contains timestamp tags: symbolic timestamp names you
can use in place of timestamps expressed in units of time. For more
information on timestamp tags, see Section 3.6.6.
You can list the timestamp tags that are in your recorded database by using the
rtirecordingservice_list_tags
script. Use the -d
argument to point
to the directory that contains your recorded database, as follows:
<NDDSHOME>/bin/rtirecordingservice_list_tags -d /database/directory/
This command will analyze the recording in /database/directory/
and list the
details of any timestamp tags in the recording, including the tag names,
descriptions, and associated timestamps.
You can use the tag_name of the timestamp tags you find in a recording when
you are creating an XML configuration file for Replay Service by using the
<data_selection>
tag.
For example, if after running rtirecordingservice_list_tags
,
you see output such as:
tag_name timestamp_ms tag_description
-------------------------- ------------- ------------------------
/my_example/my_events/tag1 1546484663309 first tag description
/my_example/my_events/tag2 1546484703360 a second tag description
Then you can have a <data_selection>
tag in your XML for Replay Service,
after the <storage>
tag, that looks like the following:
<data_selection>
<time_range>
<begin_tag>/my_example/my_events/tag1</begin_tag>
<end_tag>/my_example/my_events/tag2</end_tag>
</time_range>
</data_selection>
Replay Service will replay data between those tags. Note that when expressing a
<time_range>
tag, you can mix and match timestamps and timestamp tags.
For example, you can use a <begin_tag>
(by referring to a tag_name)
to express the time when replay should begin, and
an <end_tag>
with an end time timestamp (expressed in time units) to
express when replay should end. If you do not provide one of the bounds, then
the start of recording is the default begin bound and the end of recording
is the default end bound.
4.6.5. Jump in time in Replay Service¶
This section shows a possible scenario where the jump in time operation can be useful. This scenario will be based on the following recorded database:
sqlite3 json_recording/rti_recorder_default_json.db
sqlite> select ROWID, SampleInfo_reception_timestamp, rti_json_sample from "[email protected]";
1|1600635598310952678|{"color":"ORANGE","x":120,"y":195,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
2|1600635598460562312|{"color":"ORANGE","x":121,"y":197,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
3|1600635599612452987|{"color":"ORANGE","x":122,"y":199,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
4|1600635601310952678|{"color":"ORANGE","x":123,"y":201,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
....
1000|1600637901310952678|{"color":"ORANGE","x":129,"y":199,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
In order to reproduce this scenario, see Section 3.6.1.
Suppose you want to replay for some seconds, then you want to replay again samples 4 to 10, because they contain important events and continue the replay until the end. To do this, you need to use the jump in time operation to change the replay position, like this.
CommandReply reply;
CommandRequest request;
request.action(CommandActionKind::UPDATE_ACTION);
request.application_name(service_property.application_name());
request.resource_identifier("/playback/current_timestamp");
RTI::RecordingService::TimestampHolder timestamp_arguments;
timestamp_arguments.timestamp_nanos(1600635601310952678);
dds::topic::topic_type_support<RTI::RecordingService::TimestampHolder>
::to_cdr_buffer(
reinterpret_cast<std::vector<char> &> (request.octet_body()),
timestamp_arguments);
replay_service.execute_command(reply, request);
Once Replay Service has jumped to sample 4, it will replay those important samples again.
4.6.6. Using Debug mode in Replay Service¶
In this section you can see a possible scenario where the debug mode can be useful. This scenario will be based on the following recorded database:
sqlite3 json_recording/rti_recorder_default_json.db
sqlite> select ROWID, SampleInfo_reception_timestamp, rti_json_sample from "[email protected]";
1|1600635598310952678|{"color":"ORANGE","x":120,"y":195,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
2|1600635598460562312|{"color":"ORANGE","x":121,"y":197,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
3|1600635599612452987|{"color":"ORANGE","x":122,"y":199,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
4|1600635601310952678|{"color":"ORANGE","x":123,"y":201,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
....
100|1600637398330952678|{"color":"ORANGE","x":126,"y":199,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
101|1600637498460562312|{"color":"ORANGE","x":127,"y":199,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
102|1600637799612452987|{"color":"ORANGE","x":128,"y":199,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
103|1600637901310952678|{"color":"ORANGE","x":129,"y":199,"shapesize":30,"fillKind":"SOLID_FILL","angle":0}
In order to reproduce this scenario, see Section 3.6.1.
Suppose you want to replay samples 1-4, then you want to skip samples 5-99 and continue the replay at sample 100. To do this, you need to add some breakpoints to stop Replay Service before it replays specific samples. So you need to add two breakpoints: one after the 4th sample and another before the 100th sample.
To add those breakpoints you can use the <initial_breakpoint> tags. For example:
<debug_mode>
<enabled>true</enabled>
<initial_breakpoints>
<element label="breakpoint_afterSample4">1600635601320000000</element>
<element label="breakpoint_beforeSample100">1600637398330000000</element>
</initial_breakpoints>
</debug_mode>
Also, you can add those breakpoints by code using the remote administration system. See Section 4.4.3 for more details about the different debug mode operations.
Once the replay starts, it will hit the default breakpoint. This breakpoint is always added by Replay Service at the start timestamp of the replay.
To start the replay, we should execute the operation “continue” using remote administration. Replay Service will publish the first 4 samples, then it will stop when it hits breakpoint_afterSample4.
Once it hits this breakpoint, we don’t want to replay the 5th sample. Instead, we want to jump to the breakpoint “breakpoint_beforeSample100”, which is just before sample 100. To do this, we have to execute the operation “goto_breakpoint” like this:
CommandReply reply;
CommandRequest request;
request.action(CommandActionKind::UPDATE_ACTION);
request.application_name(service_property.application_name());
request.resource_identifier("/playback:goto_breakpoint");
RTI::RecordingService::BreakpointParams breakpoint_arguments;
breakpoint_arguments.value().labels("breakpoint_beforeSample100");
dds::topic::topic_type_support<RTI::RecordingService::BreakpointParams>
::to_cdr_buffer(
reinterpret_cast<std::vector<char> &> (request.octet_body()),
breakpoint_arguments);
replay_service.execute_command(reply, request);
Once Replay Service has jumped to “breakpoint_beforeSample100”, we need to execute the “continue” operation to resume replaying from sample 100 until the end.
4.6.7. Instance History replay¶
In this section, you will see instance history replay in action. For this scenario, we will need to record a database with a keyed type with only one instance. To do that, we will create an example from the following IDL:
struct Hello {
long id; //@key
long sample_number;
};
In order to generate the example, you need to run rtiddsgen like this:
>$NDDSHOME/bin/rtiddsgen -language C++11 -example <ARCH> Hello.idl
After generating the code, we need to make a small change in Hello_publisher.cxx,
so that it only creates an instance. To do that, set data.id
to 0, instead of
to sample_written
.
// Instance history example: Set data.id to 0.
data.id(0);
for (unsigned int samples_written = 0;
!application::shutdown_requested && samples_written < sample_count;
samples_written++) {
// Modify the data to be written here
// Instance history example: Remove this data.id set line.
// data.id(static_cast<int32_t>(samples_written));
....
To record the scenario, we need to enable Instance indexing. Please see Section 3.3.6.3 for more information.
Once we have the recorded database, we can move to the replay side. We will base the scenario on the following recorded database:
sqlite3 json_recording/rti_recorder_default.db
sqlite> select ROWID, SampleInfo_reception_timestamp from "Example [email protected]";
1|1600635598310952678
2|1600635598460562312
3|1600635599612452987
4|1600635601310952678
....
10|1600636398330952678
We need to make some changes in the replay configuration to start at sample 4 and to enable instance history replay. To do that, add the following tags:
<data_selection>
<time_range>
<begin_time>
<sec>1600635600</sec>
<nanosec>310952678</nanosec>
</begin_time>
</time_range>
</data_selection>
<playback>
<instance_history_replay>
<enabled>true</enabled>
</instance_history_replay>
</playback>
Also, the DataReader and the Replay Service DataWriter need to have their Durability QoS set to TRANSIENT_LOCAL in order to receive the historical sample. Please see Configuring QoS with XML, in the RTI Connext DDS Core Libraries User’s Manual for more information.
Once everything was set, we can run the replay scenario. The expected output is:
> ./objs/<ARCH>/Hello_subscriber
[id: 0, sample_number: 3]
[id: 0, sample_number: 4]
[id: 0, sample_number: 5]
[id: 0, sample_number: 6]
.....
[id: 0, sample_number: 10]
Notice that you also received sample 3, which was before the start timestamp. This sample was received as part of the state of the world. If we have more than one instance, we will receive one sample per instance (if they have a valid state on that start timestamp).