.. include:: ../../../recorder.4.0/srcDoc/vars.rst .. _section-replay_tutorials: Tutorials ========= .. _section-replay-getting-started: Example: Getting Started with Replay and Shapes Demo ---------------------------------------------------- Start by recording Square data, as described in :numref:`section-record-getting-started`. 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 :numref:`section-record-getting-started`. Start Replay Service ^^^^^^^^^^^^^^^^^^^^ Start |REPS|: .. code-block:: bash /bin/rtireplayservice -cfgName UserReplayServiceJson -verbosity 3 You should see Square data replayed in *Shapes Demo*. .. _section-replay-different-rate: 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 :numref:`section-record-getting-started`. Edit the Replay Configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In your ``/user_config/recording_service`` directory, edit the ``USER_REPLAY_SERVICE.xml`` file. Add a playback rate, as seen in the following XML: .. code-block:: xml JSON_SQLITE json_recording 2 0 * rti/* Start Shapes Demo ^^^^^^^^^^^^^^^^^^^^^ Use *Launcher* to start *Shapes Demo*, then create a Square subscriber. Start Replay Service ^^^^^^^^^^^^^^^^^^^^ Start |REPS|: .. code-block:: bash /bin/rtireplayservice -cfgName UserReplayService -verbosity 3 You should see Square data replayed in *Shapes Demo* twice as fast as it was originally recorded. .. _section-replay-custom-storage: 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 |RECS|, in the :link_community_examples_s:`RTI Community Recording Service examples: C storage plugin ` and :link_community_examples_s:`RTI Community Recording Service examples: C++ storage plugin `. 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 |REPS| and *Converter* that there are no more stream infos to read. - Reset: An API called by the |REPS| 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: - :link_recorder_sdk_api_c_up_5_s:`Recording Service C API documentation ` - :link_recorder_sdk_api_cpp_up_5_s:`Recording Service C++ API documentation ` .. _section-replay-tags: 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 :numref:`section-record-tags`. 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: .. code-block:: bash /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 |REPS| by using the ```` tag. For example, if after running ``rtirecordingservice_list_tags``, you see output such as: .. code-block:: bash 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 ```` tag in your XML for |REPS|, after the ```` tag, that looks like the following: .. code-block:: xml /my_example/my_events/tag1 /my_example/my_events/tag2 |REPS| will replay data between those tags. Note that when expressing a ```` tag, you can mix and match timestamps and timestamp tags. For example, you can use a ```` (by referring to a tag_name) to express the time when replay should begin, and an ```` 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. 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: .. code-block:: bash 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 :numref:`section-record-getting-started`. 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. .. code-block:: C++ 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 ::to_cdr_buffer( reinterpret_cast &> (request.octet_body()), timestamp_arguments); replay_service.execute_command(reply, request); Once |REPS| has jumped to sample 4, it will replay those important samples again. 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: .. code-block:: bash 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 :numref:`section-record-getting-started`. 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 |REPS| 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 tags. For example: .. code-block:: xml true 1600635601320000000 1600637398330000000 Also, you can add those breakpoints by code using the remote administration system. See :numref:`section-replay_remote_commands` 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 |REPS| at the start timestamp of the replay. To start the replay, we should execute the operation "continue" using remote administration. |REPS| 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: .. code-block:: C++ 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 ::to_cdr_buffer( reinterpret_cast &> (request.octet_body()), breakpoint_arguments); replay_service.execute_command(reply, request); Once |REPS| has jumped to "breakpoint_beforeSample100", we need to execute the "continue" operation to resume replaying from sample 100 until the end. 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: .. code-block:: bash struct Hello { long id; //@key long sample_number; }; In order to generate the example, you need to run rtiddsgen like this: .. code-block:: bash >$NDDSHOME/bin/rtiddsgen -language C++11 -example 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``. .. code-block:: C++ // 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(samples_written)); .... To record the scenario, we need to enable Instance indexing. Please see :numref:`section-record-config-instance-indexing-tag` 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: .. code-block:: bash 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: .. code-block:: xml 1600635600 310952678 true Also, the DataReader and the |REPS| DataWriter need to have their Durability QoS set to TRANSIENT_LOCAL in order to receive the historical sample. Please see :link_configuring_qos_xml_up_5:`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: .. code-block:: bash > ./objs//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).