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 "Square@0";

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.

CommandRequest request;

// Fill the request
request.action(CommandActionKind::UPDATE_ACTION);
request.application_name(service_property.application_name());
request.resource_identifier("/playback/current_timestamp");

// Set TimestampHolder
RTI::RecordingService::TimestampHolder timestamp_arguments;
timestamp_arguments.timestamp_nanos(1600635601310952678);

// Seralization of the TimestampHolder
dds::topic::topic_type_support<RTI::RecordingService::TimestampHolder>
        ::to_cdr_buffer(
                reinterpret_cast<std::vector<char> &> (request.octet_body()),
                timestamp_arguments);

// Send command
command_requester->send_request(request);

Once Replay Service has jumped to sample 4, it will replay those important samples again.

A simple C++ example of how to use the remote administration API is available here: RTI Community Recording Service examples: Service Administration. If you want to reproduce this scenario using this example you can run the following command:

./Requester UPDATE /replay_services/remote_admin/playback/current_timestamp --current-timestamp 1600635601310952678

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 "Square@0";

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. You can run the operation “continue” like this:

CommandRequest request;

// Fill the request
request.action(CommandActionKind::UPDATE_ACTION);
request.application_name(service_property.application_name());
request.resource_identifier("/playback:continue");

// Send command
command_requester->send_request(request);

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:

CommandRequest request;

// Fill the request
request.action(CommandActionKind::UPDATE_ACTION);
request.application_name(service_property.application_name());
request.resource_identifier("/playback:goto_breakpoint");

// Set BreakpointParams
RTI::RecordingService::BreakpointParams breakpoint_arguments;
breakpoint_arguments.value().labels("breakpoint_beforeSample100");

// Seralization of the BreakpointParams
dds::topic::topic_type_support<RTI::RecordingService::BreakpointParams>
        ::to_cdr_buffer(
                reinterpret_cast<std::vector<char> &> (request.octet_body()),
                breakpoint_arguments);

// Send command
command_requester->send_request(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.

A simple C++ example of how to use the remote administration API is available here: RTI Community Recording Service examples: Service Administration. If you want to reproduce this scenario using this example you can run the following command:

./Requester UPDATE /replay_services/remote_admin/playback:continue
./Requester UPDATE /replay_services/remote_admin/playback:goto_breakpoint --goto-breakpoint "breakpoint_beforeSample100"
./Requester UPDATE /replay_services/remote_admin/playback:continue

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 Hello@0";

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).