Expected Runtimes

When running under the simclock executor, all callbacks (subscribers or timers) have a simulated execution time of one nanosecond. That is, no matter how much time they take to execute in the real world, in simulation, we pretend they completed in one nanosecond.

It can be very useful, however, to simulate how long the callbacks would take to execute in the real world. For example, if you have a perception callback that takes 200ms to execute, but is listening for messages that arrive every 100ms, this would allow you to properly model what happens when every other message is dropped.

Additionally, setting expected runtimes allows the simclock executor to find additional opportunities for parallelism. The executor will always ensure determinism in how callbacks are fired, but if it knows that a callback takes “200 simulated milliseconds” to run, then it can run other callbacks simultaneously.

All normal rules for callback execution apply; you can not run two or more callbacks from the same stage simultaneously.

Automatic Generation

The easiest way to generate expected runtimes for your pipeline is to run with the --collect-runtimes command line option:

./my_sample_pipeline --collect-runtimes ./my_runtimes.bin

This will run the pipeline as usual, but will monitor how long every callback takes to execute. It will automatically make a guess as to the ’expected’ runtime for the callback, and they will be stored in the path given (in this case, ./my_runtimes.bin).

The runtimes will of course be heavily dependent on the hardware used to run the pipeline; consider running it on representative hardware. Additionally, even then, they will only represent runtimes of your modules in the circumstances that the pipeline was run under (in other words, it is essentially coming up with a weighted average over the pipeline run, and this may or may not be representative of all situations your pipeline runs in).

This command line option is available if you are using the main_offboard module; otherwise, you can gain it by manually adding the CollectRuntimesStage to your pipeline.

Log Generation

You can also process a data log to generate runtimes based on the information that was logged. This requires you to log the pipeline metrics, and then to run:

./build/generate_runtimes /path/to/log /path/to/output.bin

Otherwise, this is identical to the automatic generation step.

Manual Setting

It’s possible to either just set the expected runtimes manually, or to override whatever the runtimes files are providing. In either case, you can do so by setting the expected_runtime field of your periodic timer configuration or subscriber configuration.

This sets the subscriber for /my/channel_name to 100ms:

ark::pipeline::SubscriberConfiguration<MyMessage> config;

config.channel_name = "/my/channel_name";
config.maximum_queue_depth = 10;
config.callback = [this](const auto &msg) { this->handle_messaage(msg); };
config.expected_runtime = std::chrono::milliseconds{100};

interface.add_subscriber(config);

And this does the same for a periodic timer:

ark::pipeline::PeriodicTimerConfiguration config;

config.name = "MyTimerName";
config.callback = [this](const auto &now) { this->handle_timer(now); };
config.rate = std::chrono::seconds{1};
config.expected_runtime = std::chrono::milliseconds{100};

interface.add_timer(config);

Note that setting expected runtimes for event-based timers is currently not supported; it is assumed that they are “driving” a simulation pipeline (such as a log player), and need to execute, for practical purposes, instantaneously.

Runtime File Usage

You can make use of the generated runtimes by adding a section to your simclock executor configuration:

package:
  configs:
    Pipeline:
      metadata:
        simclock_config:
          expected_runtime_config_path: "./my_runtimes.bin"

You can also use runtime files in a “one off” manner by using the --input-runtimes-path command line argument. The config file and command line arguments are equivalent.

This will cause the simclock executor to automatically apply all of the runtimes stored in that file to all of the subscriber/timer callbacks currently in your pipeline.

Any subscriber/timer not found in the file will be left with their default values.