changes to get running on pi + traces
This commit is contained in:
parent
a965066d94
commit
bf3f7ff485
29 changed files with 253 additions and 2887 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -73,7 +73,8 @@
|
||||||
"codecvt": "cpp",
|
"codecvt": "cpp",
|
||||||
"forward_list": "cpp",
|
"forward_list": "cpp",
|
||||||
"valarray": "cpp",
|
"valarray": "cpp",
|
||||||
"ddsi_sertopic.h": "c"
|
"ddsi_sertopic.h": "c",
|
||||||
|
"regex": "cpp"
|
||||||
},
|
},
|
||||||
"cmake.ignoreCMakeListsMissing": true,
|
"cmake.ignoreCMakeListsMissing": true,
|
||||||
"python.autoComplete.extraPaths": [
|
"python.autoComplete.extraPaths": [
|
||||||
|
|
25
build.sh
25
build.sh
|
@ -1,17 +1,16 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -eo pipefail
|
colcon build --packages-select tracetools --allow-overriding tracetools && \
|
||||||
|
source install/setup.bash && \
|
||||||
|
colcon build --packages-select cyclonedds --allow-overriding cyclonedds && \
|
||||||
|
source install/setup.bash && \
|
||||||
|
colcon build --packages-select rmw_cyclonedds_cpp --allow-overriding rmw_cyclonedds_cpp && \
|
||||||
|
source install/setup.bash && \
|
||||||
|
colcon build --packages-select rcl rcl_yaml_param_parser --allow-overriding rcl rcl_yaml_param_parser && \
|
||||||
|
source install/setup.bash && \
|
||||||
|
colcon build --packages-select rclcpp --allow-overriding rclcpp && \
|
||||||
|
source install/setup.bash && \
|
||||||
|
colcon build --packages-up-to full_topology && \
|
||||||
|
source install/setup.bash
|
||||||
|
|
||||||
colcon build --packages-select tracetools
|
|
||||||
source install/setup.bash
|
|
||||||
colcon build --packages-select cyclonedds
|
|
||||||
source install/setup.bash
|
|
||||||
colcon build --packages-select rmw_cyclonedds_cpp
|
|
||||||
source install/setup.bash
|
|
||||||
colcon build --packages-select rcl rcl_yaml_param_parser
|
|
||||||
source install/setup.bash
|
|
||||||
colcon build --packages-select rclcpp
|
|
||||||
source install/setup.bash
|
|
||||||
colcon build
|
|
||||||
source install/setup.bash
|
|
||||||
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
||||||
|
|
9
build_run_copy.sh
Executable file
9
build_run_copy.sh
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
colcon build --packages-select full_topology && \
|
||||||
|
source install/setup.bash && \
|
||||||
|
TRACE_ID=$(taskset -c 2,3 ros2 launch full_topology trace_full_topology.launch.py length:=100 | \
|
||||||
|
grep -oP '(?<=Writing tracing session to: /home/niklas/ROS-Dynamic-Executor-Experiments/analysis/tracing/full_topology_tracing-)\d+') && \
|
||||||
|
ros2 trace-analysis convert "/home/niklas/ROS-Dynamic-Executor-Experiments/analysis/tracing/full_topology_tracing-${TRACE_ID}/ust" && \
|
||||||
|
scp -r "analysis/tracing/full_topology_tracing-${TRACE_ID}/" 192.168.162.249:/home/niklas/dataflow-analysis/traces/ && \
|
||||||
|
echo "Trace analysis \"${TRACE_ID}\" complete and copied to remote server."
|
|
@ -3,7 +3,7 @@
|
||||||
"symlink-install": true,
|
"symlink-install": true,
|
||||||
"cmake-args": [
|
"cmake-args": [
|
||||||
"-DCMAKE_BUILD_TYPE=RelWithDebInfo",
|
"-DCMAKE_BUILD_TYPE=RelWithDebInfo",
|
||||||
"-DCMAKE_EXPORT_COMPILE_COMMANDS=True",
|
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
|
||||||
"-GNinja",
|
"-GNinja",
|
||||||
# clang
|
# clang
|
||||||
"-DCMAKE_C_COMPILER=clang-18",
|
"-DCMAKE_C_COMPILER=clang-18",
|
||||||
|
@ -14,4 +14,4 @@
|
||||||
"event-handlers": ["console_cohesion+"],
|
"event-handlers": ["console_cohesion+"],
|
||||||
# "mixin": "clang",
|
# "mixin": "clang",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
5
init.sh
Normal file
5
init.sh
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
source /opt/ros/foxy/setup.bash
|
||||||
|
source ./venv38/bin/activate
|
||||||
|
|
||||||
|
export MAKEFLAGS="-j 1"
|
||||||
|
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
|
|
@ -1,78 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.8)
|
|
||||||
project(casestudy VERSION 0.1.0)
|
|
||||||
|
|
||||||
# Set C++ standards
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
|
|
||||||
# Compiler options
|
|
||||||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
||||||
add_compile_options(-Wall -Wextra -Wpedantic)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Find dependencies
|
|
||||||
find_package(ament_cmake REQUIRED)
|
|
||||||
find_package(tracetools REQUIRED)
|
|
||||||
find_package(rclcpp REQUIRED)
|
|
||||||
find_package(priority_executor REQUIRED)
|
|
||||||
find_package(simple_timer REQUIRED)
|
|
||||||
find_package(jsoncpp REQUIRED)
|
|
||||||
find_package(casestudy_tools REQUIRED)
|
|
||||||
|
|
||||||
# Define experiment targets
|
|
||||||
set(EXPERIMENTS
|
|
||||||
casestudy_2023customfile_singlethread
|
|
||||||
casestudy_2024ours_latency
|
|
||||||
casestudy_2024ours_executor2executor
|
|
||||||
casestudy_example
|
|
||||||
casestudy_monitored_example
|
|
||||||
casestudy_fire_drone
|
|
||||||
)
|
|
||||||
|
|
||||||
# Function to create experiment targets
|
|
||||||
function(new_experiment experiment_name)
|
|
||||||
add_executable(${experiment_name} src/${experiment_name}.cpp)
|
|
||||||
target_include_directories(${experiment_name} PUBLIC
|
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
||||||
$<INSTALL_INTERFACE:include>
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add dependencies with ament
|
|
||||||
ament_target_dependencies(${experiment_name}
|
|
||||||
tracetools
|
|
||||||
simple_timer
|
|
||||||
casestudy_tools
|
|
||||||
rclcpp
|
|
||||||
priority_executor
|
|
||||||
)
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# Create all experiment targets
|
|
||||||
foreach(experiment ${EXPERIMENTS})
|
|
||||||
new_experiment(${experiment})
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
# Special handling for targets with additional dependencies
|
|
||||||
target_link_libraries(casestudy_2023customfile_singlethread jsoncpp)
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
install(
|
|
||||||
TARGETS ${EXPERIMENTS}
|
|
||||||
RUNTIME DESTINATION lib/${PROJECT_NAME}
|
|
||||||
)
|
|
||||||
|
|
||||||
install(
|
|
||||||
DIRECTORY include/
|
|
||||||
DESTINATION include
|
|
||||||
)
|
|
||||||
|
|
||||||
# Testing
|
|
||||||
if(BUILD_TESTING)
|
|
||||||
find_package(ament_lint_auto REQUIRED)
|
|
||||||
ament_lint_auto_find_test_dependencies()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Export and package configuration
|
|
||||||
ament_export_include_directories(include)
|
|
||||||
ament_package()
|
|
3
src/casestudy/include/.gitignore
vendored
3
src/casestudy/include/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
# We don't have content in this dir yet, but want it in git anyways
|
|
||||||
# So we add a non-empty .gitignore file, explicitly not ignoring itself
|
|
||||||
!.gitignore
|
|
|
@ -1,29 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
|
||||||
<package format="3">
|
|
||||||
<name>casestudy</name>
|
|
||||||
<version>0.1.0</version>
|
|
||||||
<description>
|
|
||||||
ROS 2 case study package for evaluating different executor implementations and timing behaviors
|
|
||||||
</description>
|
|
||||||
<maintainer email="kurt4wilson@gmail.com">kurt</maintainer>
|
|
||||||
<license>Apache License 2.0</license>
|
|
||||||
|
|
||||||
<buildtool_depend>ament_cmake</buildtool_depend>
|
|
||||||
|
|
||||||
<!-- Build Dependencies -->
|
|
||||||
<depend>tracetools</depend>
|
|
||||||
<depend>rclcpp</depend>
|
|
||||||
<depend>priority_executor</depend>
|
|
||||||
<depend>simple_timer</depend>
|
|
||||||
<depend>casestudy_tools</depend>
|
|
||||||
<depend>jsoncpp</depend>
|
|
||||||
|
|
||||||
<!-- Test Dependencies -->
|
|
||||||
<test_depend>ament_lint_auto</test_depend>
|
|
||||||
<test_depend>ament_lint_common</test_depend>
|
|
||||||
|
|
||||||
<export>
|
|
||||||
<build_type>ament_cmake</build_type>
|
|
||||||
</export>
|
|
||||||
</package>
|
|
|
@ -1,164 +0,0 @@
|
||||||
#include "rclcpp/rclcpp.hpp"
|
|
||||||
#include "priority_executor/priority_executor.hpp"
|
|
||||||
#include "priority_executor/priority_memory_strategy.hpp"
|
|
||||||
#include "casestudy_tools/test_nodes.hpp"
|
|
||||||
#include "std_msgs/msg/string.hpp"
|
|
||||||
#include "casestudy_tools/primes_workload.hpp"
|
|
||||||
#include "casestudy_tools/experiment.hpp"
|
|
||||||
#include "jsoncpp/json/reader.h"
|
|
||||||
#include <string>
|
|
||||||
#include <fstream>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
|
|
||||||
std::atomic<bool> should_do_task(true);
|
|
||||||
|
|
||||||
void run_one_executor(
|
|
||||||
std::function<void(std::atomic<bool>&)> const& exec_fun,
|
|
||||||
int const idx) {
|
|
||||||
(void)idx;
|
|
||||||
|
|
||||||
// set this process to highest priority
|
|
||||||
struct sched_param param;
|
|
||||||
param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 1;
|
|
||||||
int const result = sched_setscheduler(0, SCHED_FIFO, ¶m);
|
|
||||||
if (result != 0) {
|
|
||||||
std::cout << "ros_experiment: sched_setscheduler failed: " << result
|
|
||||||
<< ": " << strerror(errno) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set process name
|
|
||||||
prctl(PR_SET_NAME, "ros_experiment", 0, 0, 0);
|
|
||||||
exec_fun(should_do_task);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ros_experiment(
|
|
||||||
int const argc,
|
|
||||||
char** const argv,
|
|
||||||
std::string const& file_name,
|
|
||||||
ExperimentConfig config) {
|
|
||||||
|
|
||||||
rclcpp::init(argc, argv);
|
|
||||||
|
|
||||||
// we have to create a node to process any passed parameters
|
|
||||||
auto const node = rclcpp::Node::make_shared("experiment_node");
|
|
||||||
node->declare_parameter("executor_type", "edf");
|
|
||||||
|
|
||||||
std::string executor_type;
|
|
||||||
node->get_parameter("executor_type", executor_type);
|
|
||||||
std::cout << "using executor type: " << executor_type << std::endl;
|
|
||||||
|
|
||||||
config.executor_type = executor_type;
|
|
||||||
config.nodes.push_back(node);
|
|
||||||
|
|
||||||
Experiment experiment(config);
|
|
||||||
auto const exec_funs = experiment.getRunFunctions();
|
|
||||||
std::cout << "got " << exec_funs.size() << " executors" << std::endl;
|
|
||||||
|
|
||||||
std::vector<std::thread> exec_threads;
|
|
||||||
int i = 0;
|
|
||||||
experiment.resetTimers();
|
|
||||||
|
|
||||||
for (auto const& exec_fun : exec_funs) {
|
|
||||||
exec_threads.emplace_back(run_one_executor, exec_fun, i++);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& t : exec_threads) {
|
|
||||||
t.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
experiment.writeLogsToFile(file_name, "2023customfile_singlethread");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
std::ifstream input_file("taskset.json");
|
|
||||||
|
|
||||||
if (!input_file.is_open()) {
|
|
||||||
std::cerr << "Error opening file!" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Json::Value root;
|
|
||||||
Json::Reader reader;
|
|
||||||
bool const parsingSuccessful = reader.parse(input_file, root);
|
|
||||||
|
|
||||||
if (!parsingSuccessful) {
|
|
||||||
std::cerr << "Error parsing file!" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set ourselves to the second highest priority
|
|
||||||
struct sched_param param;
|
|
||||||
param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 2;
|
|
||||||
int const result = sched_setscheduler(0, SCHED_FIFO, ¶m);
|
|
||||||
if (result != 0) {
|
|
||||||
std::cout << "ros_experiment: sched_setscheduler failed: " << result
|
|
||||||
<< ": " << strerror(errno) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// calibrate the dummy load for the current system
|
|
||||||
calibrate_dummy_load();
|
|
||||||
|
|
||||||
ExperimentConfig config;
|
|
||||||
|
|
||||||
for (uint i = 0; i < root["chain_lengths"].size(); i++) {
|
|
||||||
config.chain_lengths.push_back(root["chain_lengths"][i].asUInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint i = 0; i < root["node_ids"].size(); i++) {
|
|
||||||
std::vector<uint32_t> node_ids_row;
|
|
||||||
for (uint j = 0; j < root["node_ids"][i].size(); j++) {
|
|
||||||
node_ids_row.push_back(root["node_ids"][i][j].asUInt());
|
|
||||||
}
|
|
||||||
config.callback_ids.push_back(std::move(node_ids_row));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint i = 0; i < root["node_priorities"].size(); i++) {
|
|
||||||
config.callback_priorities.push_back(root["node_priorities"][i].asUInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
config.num_groups = 0;
|
|
||||||
|
|
||||||
for (uint i = 0; i < root["chain_timer_control"].size(); i++) {
|
|
||||||
config.chain_timer_control.push_back(root["chain_timer_control"][i].asInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint i = 0; i < root["node_runtimes"].size(); i++) {
|
|
||||||
config.callback_runtimes.push_back(root["node_runtimes"][i].asDouble());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint i = 0; i < root["chain_periods"].size(); i++) {
|
|
||||||
config.chain_periods.push_back(root["chain_periods"][i].asInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint i = 0; i < root["node_executor_assignments"].size(); i++) {
|
|
||||||
config.callback_executor_assignments.push_back(root["node_executor_assignments"][i].asUInt() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint i = 0; i < root["executor_to_cpu_core"].size(); i++) {
|
|
||||||
config.executor_to_cpu_assignments.push_back(root["executor_to_cpu_core"][i].asUInt() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.parallel_mode = false;
|
|
||||||
config.cores = 4;
|
|
||||||
|
|
||||||
int const i = (100 - 35) / 5;
|
|
||||||
int const ros_budget = 35 + i * 5;
|
|
||||||
int const other_task_budget = 65 - i * 5;
|
|
||||||
std::cout << "ROS budget: " << ros_budget
|
|
||||||
<< " Other task budget: " << other_task_budget << std::endl;
|
|
||||||
|
|
||||||
should_do_task.store(true);
|
|
||||||
double const other_task_time = 80 * other_task_budget / 100.0;
|
|
||||||
std::thread other_task(do_other_task, other_task_time, 80, std::ref(should_do_task));
|
|
||||||
|
|
||||||
std::string const file_name = "ros_" + std::to_string(ros_budget);
|
|
||||||
std::thread ros_task(ros_experiment, argc, argv, file_name, config);
|
|
||||||
std::cout << "tasks started" << std::endl;
|
|
||||||
ros_task.join();
|
|
||||||
other_task.join();
|
|
||||||
std::cout << "tasks joined" << std::endl;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,197 +0,0 @@
|
||||||
#include "casestudy_tools/experiment.hpp"
|
|
||||||
#include "casestudy_tools/primes_workload.hpp"
|
|
||||||
#include <string>
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
// bool should_do_task = true;
|
|
||||||
std::atomic<bool> should_do_task(true);
|
|
||||||
|
|
||||||
void run_one_executor(
|
|
||||||
std::function<void(std::atomic<bool>&)> const& exec_fun,
|
|
||||||
int const idx) {
|
|
||||||
(void)idx;
|
|
||||||
// set this process to highest priority
|
|
||||||
// struct sched_param param;
|
|
||||||
// param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 1;
|
|
||||||
// int result = sched_setscheduler(0, SCHED_FIFO, ¶m);
|
|
||||||
// if (result != 0)
|
|
||||||
// {
|
|
||||||
// std::cout << "ros_experiment: sched_setscheduler failed: " << result <<
|
|
||||||
// ": " << strerror(errno) << std::endl;
|
|
||||||
// }
|
|
||||||
sched_attr rt_attr;
|
|
||||||
rt_attr.size = sizeof(rt_attr);
|
|
||||||
rt_attr.sched_flags = 0;
|
|
||||||
rt_attr.sched_nice = 0;
|
|
||||||
rt_attr.sched_priority = 99;
|
|
||||||
rt_attr.sched_policy = SCHED_FIFO;
|
|
||||||
rt_attr.sched_runtime = 0;
|
|
||||||
rt_attr.sched_period = 0;
|
|
||||||
|
|
||||||
int const result = sched_setattr(0, &rt_attr, 0);
|
|
||||||
if (result != 0) {
|
|
||||||
std::cout << "executor task: could not set scheduler: " << result << ": "
|
|
||||||
<< strerror(errno) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set process name
|
|
||||||
prctl(PR_SET_NAME, "ros_experiment", 0, 0, 0);
|
|
||||||
exec_fun(should_do_task);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ros_experiment(
|
|
||||||
rclcpp::Node::SharedPtr const node,
|
|
||||||
std::string const& file_name,
|
|
||||||
ExperimentConfig config) {
|
|
||||||
|
|
||||||
std::string executor_type;
|
|
||||||
node->get_parameter("executor_type", executor_type);
|
|
||||||
int num_subscribers;
|
|
||||||
node->get_parameter("num_subscribers", num_subscribers);
|
|
||||||
int num_chains;
|
|
||||||
node->get_parameter("num_chains", num_chains);
|
|
||||||
std::cout << "using executor type: " << executor_type << std::endl;
|
|
||||||
|
|
||||||
// TODO: move experiment configuration to main
|
|
||||||
config.executor_type = executor_type;
|
|
||||||
config.nodes.push_back(node);
|
|
||||||
|
|
||||||
Experiment experiment(config);
|
|
||||||
// std::string result_log = experiment.run(should_do_task);
|
|
||||||
auto const exec_funs = experiment.getRunFunctions();
|
|
||||||
std::cout << "got " << exec_funs.size() << " executors" << std::endl;
|
|
||||||
std::vector<std::thread> exec_threads;
|
|
||||||
int i = 0;
|
|
||||||
experiment.resetTimers();
|
|
||||||
|
|
||||||
for (auto const& exec_fun : exec_funs) {
|
|
||||||
exec_threads.emplace_back(run_one_executor, exec_fun, i++);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& t : exec_threads) {
|
|
||||||
t.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string outputname = "2024ours_executor2executor_" +
|
|
||||||
std::to_string(num_subscribers) + "_" +
|
|
||||||
std::to_string(num_chains);
|
|
||||||
experiment.writeLogsToFile(file_name, outputname);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
rclcpp::init(argc, argv);
|
|
||||||
// we have to create a node to process any passed parameters
|
|
||||||
auto const node = rclcpp::Node::make_shared("experiment_node");
|
|
||||||
// auto node = rclcpp::Node::make_shared("experiment_node",
|
|
||||||
// rclcpp::NodeOptions().use_intra_process_comms(true));
|
|
||||||
node->declare_parameter("executor_type", "edf");
|
|
||||||
node->declare_parameter("num_subscribers", 3);
|
|
||||||
node->declare_parameter("num_chains", 1);
|
|
||||||
node->declare_parameter("runtime", 0);
|
|
||||||
|
|
||||||
int num_s;
|
|
||||||
node->get_parameter("num_subscribers", num_s);
|
|
||||||
int runtime;
|
|
||||||
node->get_parameter("runtime", runtime);
|
|
||||||
int num_chains;
|
|
||||||
node->get_parameter("num_chains", num_chains);
|
|
||||||
|
|
||||||
// set ourselves to the second highest priority
|
|
||||||
// struct sched_param param;
|
|
||||||
// param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 2;
|
|
||||||
// int result = sched_setscheduler(0, SCHED_FIFO, ¶m);
|
|
||||||
sched_attr rt_attr;
|
|
||||||
rt_attr.size = sizeof(rt_attr);
|
|
||||||
rt_attr.sched_policy = SCHED_FIFO;
|
|
||||||
// rt_attr.sched_priority = sched_get_priority_max(SCHED_FIFO) - 2;
|
|
||||||
rt_attr.sched_priority = 99;
|
|
||||||
int const result = sched_setattr(0, &rt_attr, 0);
|
|
||||||
|
|
||||||
if (result != 0) {
|
|
||||||
std::cout << "ros_experiment: sched_setscheduler failed: " << result << ": "
|
|
||||||
<< strerror(errno) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// calibrate the dummy load for the current system
|
|
||||||
calibrate_dummy_load();
|
|
||||||
|
|
||||||
ExperimentConfig config;
|
|
||||||
|
|
||||||
// runtimes and timers subject to change
|
|
||||||
config.chain_lengths = {};
|
|
||||||
config.callback_ids = {};
|
|
||||||
config.chain_timer_control = {};
|
|
||||||
config.chain_periods = {};
|
|
||||||
config.callback_runtimes = {};
|
|
||||||
config.callback_executor_assignments = {};
|
|
||||||
int node_id = 0;
|
|
||||||
for (int c = 0; c < num_chains; c++) {
|
|
||||||
config.callback_ids.push_back({});
|
|
||||||
for (int i = 0; i < num_s + 1; i++) {
|
|
||||||
config.callback_ids[c].push_back(node_id);
|
|
||||||
if (i == 0) {
|
|
||||||
// first node is the publisher, and goes in the first executor
|
|
||||||
config.callback_executor_assignments.push_back(0);
|
|
||||||
} else {
|
|
||||||
// all other nodes go in the second executor
|
|
||||||
config.callback_executor_assignments.push_back(1);
|
|
||||||
}
|
|
||||||
node_id++;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < num_s + 1; i++) {
|
|
||||||
config.callback_priorities.push_back(i);
|
|
||||||
config.callback_runtimes.push_back(runtime);
|
|
||||||
}
|
|
||||||
config.chain_lengths.push_back(num_s+1);
|
|
||||||
config.chain_periods.push_back(10);
|
|
||||||
config.chain_timer_control.push_back(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.executor_to_cpu_assignments = {0, 1};
|
|
||||||
|
|
||||||
std::cout << "node ids: " << std::endl;
|
|
||||||
for (size_t i = 0; i < config.callback_ids.size(); i++) {
|
|
||||||
for (size_t j = 0; j < config.callback_ids[i].size(); j++) {
|
|
||||||
std::cout << config.callback_ids[i][j] << " ";
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
config.num_groups = 0;
|
|
||||||
config.group_memberships = {}; // no groups
|
|
||||||
|
|
||||||
config.parallel_mode = false;
|
|
||||||
config.cores = 1;
|
|
||||||
config.special_cases.push_back("2024ours_latency");
|
|
||||||
|
|
||||||
// sanity_check_config(config);
|
|
||||||
|
|
||||||
// from 35/65 to 100/0 in increments of 5
|
|
||||||
// for (int i = 0; i <= (100 - 35) / 5; i++)
|
|
||||||
// {
|
|
||||||
// for now, run with 100% ros
|
|
||||||
int const i = (100 - 35) / 5;
|
|
||||||
int const ros_budget = 35 + i * 5;
|
|
||||||
int const other_task_budget = 65 - i * 5;
|
|
||||||
std::cout << "ROS budget: " << ros_budget
|
|
||||||
<< " Other task budget: " << other_task_budget << std::endl;
|
|
||||||
// do other task in a separate thread
|
|
||||||
should_do_task.store(true);
|
|
||||||
|
|
||||||
// do ros task
|
|
||||||
std::string const file_name = "ros_" + std::to_string(ros_budget);
|
|
||||||
// ros_experiment(argc, argv, file_name);
|
|
||||||
std::thread ros_task(ros_experiment, node, file_name, config);
|
|
||||||
std::cout << "tasks started" << std::endl;
|
|
||||||
ros_task.join();
|
|
||||||
std::cout << "tasks joined" << std::endl;
|
|
||||||
|
|
||||||
// // run once
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
#include "casestudy_tools/experiment.hpp"
|
|
||||||
#include "casestudy_tools/primes_workload.hpp"
|
|
||||||
#include <string>
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
// bool should_do_task = true;
|
|
||||||
std::atomic<bool> should_do_task(true);
|
|
||||||
|
|
||||||
void run_one_executor(std::function<void(std::atomic<bool>&)> const& exec_fun,
|
|
||||||
int const idx) {
|
|
||||||
(void)idx;
|
|
||||||
// set this process to highest priority
|
|
||||||
// struct sched_param param;
|
|
||||||
// param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 1;
|
|
||||||
// int result = sched_setscheduler(0, SCHED_FIFO, ¶m);
|
|
||||||
// if (result != 0)
|
|
||||||
// {
|
|
||||||
// std::cout << "ros_experiment: sched_setscheduler failed: " << result <<
|
|
||||||
// ": " << strerror(errno) << std::endl;
|
|
||||||
// }
|
|
||||||
sched_attr rt_attr;
|
|
||||||
rt_attr.size = sizeof(rt_attr);
|
|
||||||
rt_attr.sched_flags = 0;
|
|
||||||
rt_attr.sched_nice = 0;
|
|
||||||
rt_attr.sched_priority = 99;
|
|
||||||
rt_attr.sched_policy = SCHED_FIFO;
|
|
||||||
rt_attr.sched_runtime = 0;
|
|
||||||
rt_attr.sched_period = 0;
|
|
||||||
int const result = sched_setattr(0, &rt_attr, 0);
|
|
||||||
if (result != 0) {
|
|
||||||
std::cout << "executor task: could not set scheduler: " << result << ": "
|
|
||||||
<< strerror(errno) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set process name
|
|
||||||
prctl(PR_SET_NAME, "ros_experiment", 0, 0, 0);
|
|
||||||
|
|
||||||
exec_fun(should_do_task);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ros_experiment(rclcpp::Node::SharedPtr const& node, std::string const& file_name,
|
|
||||||
ExperimentConfig config) {
|
|
||||||
std::string executor_type;
|
|
||||||
node->get_parameter("executor_type", executor_type);
|
|
||||||
int num_subscribers;
|
|
||||||
node->get_parameter("num_subscribers", num_subscribers);
|
|
||||||
int num_chains;
|
|
||||||
node->get_parameter("num_chains", num_chains);
|
|
||||||
std::cout << "using executor type: " << executor_type << std::endl;
|
|
||||||
|
|
||||||
// TODO: move experiment configuration to main
|
|
||||||
config.executor_type = executor_type;
|
|
||||||
config.nodes.push_back(node);
|
|
||||||
|
|
||||||
Experiment experiment(config);
|
|
||||||
// std::string result_log = experiment.run(should_do_task);
|
|
||||||
std::vector<std::function<void(std::atomic<bool>&)>> exec_funs =
|
|
||||||
experiment.getRunFunctions();
|
|
||||||
std::cout << "got " << exec_funs.size() << " executors" << std::endl;
|
|
||||||
std::vector<std::thread> exec_threads;
|
|
||||||
int i = 0;
|
|
||||||
experiment.resetTimers();
|
|
||||||
for (auto const& exec_fun : exec_funs) {
|
|
||||||
exec_threads.push_back(std::thread(run_one_executor, exec_fun, i));
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& t : exec_threads) {
|
|
||||||
t.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string const outputname = "2024ours_latency_" +
|
|
||||||
std::to_string(num_subscribers) + "_" + std::to_string(num_chains);
|
|
||||||
experiment.writeLogsToFile(file_name, outputname);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
rclcpp::init(argc, argv);
|
|
||||||
// we have to create a node to process any passed parameters
|
|
||||||
auto const node = rclcpp::Node::make_shared("experiment_node");
|
|
||||||
// auto node = rclcpp::Node::make_shared("experiment_node",
|
|
||||||
// rclcpp::NodeOptions().use_intra_process_comms(true));
|
|
||||||
node->declare_parameter("executor_type", "edf");
|
|
||||||
node->declare_parameter("num_subscribers", 3);
|
|
||||||
node->declare_parameter("num_chains", 1);
|
|
||||||
node->declare_parameter("runtime", 0);
|
|
||||||
|
|
||||||
int num_s;
|
|
||||||
node->get_parameter("num_subscribers", num_s);
|
|
||||||
int runtime;
|
|
||||||
node->get_parameter("runtime", runtime);
|
|
||||||
int num_chains;
|
|
||||||
node->get_parameter("num_chains", num_chains);
|
|
||||||
|
|
||||||
// set ourselves to the second highest priority
|
|
||||||
// struct sched_param param;
|
|
||||||
// param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 2;
|
|
||||||
// int result = sched_setscheduler(0, SCHED_FIFO, ¶m);
|
|
||||||
sched_attr rt_attr;
|
|
||||||
rt_attr.size = sizeof(rt_attr);
|
|
||||||
rt_attr.sched_policy = SCHED_FIFO;
|
|
||||||
// rt_attr.sched_priority = sched_get_priority_max(SCHED_FIFO) - 2;
|
|
||||||
rt_attr.sched_priority = 99;
|
|
||||||
int const result = sched_setattr(0, &rt_attr, 0);
|
|
||||||
|
|
||||||
if (result != 0) {
|
|
||||||
std::cout << "ros_experiment: sched_setscheduler failed: " << result << ": "
|
|
||||||
<< strerror(errno) << std::endl;
|
|
||||||
}
|
|
||||||
// calibrate the dummy load for the current system
|
|
||||||
calibrate_dummy_load();
|
|
||||||
|
|
||||||
ExperimentConfig config;
|
|
||||||
|
|
||||||
// runtimes and timers subject to change
|
|
||||||
config.chain_lengths = {};
|
|
||||||
config.callback_ids = {};
|
|
||||||
config.chain_timer_control = {};
|
|
||||||
config.chain_periods = {};
|
|
||||||
config.callback_runtimes = {};
|
|
||||||
int node_id = 0;
|
|
||||||
for (int c = 0; c < num_chains; c++) {
|
|
||||||
config.callback_ids.push_back({});
|
|
||||||
for (int i = 0; i < num_s + 1; i++) {
|
|
||||||
config.callback_ids[c].push_back(node_id);
|
|
||||||
node_id++;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < num_s + 1; i++) {
|
|
||||||
config.callback_priorities.push_back(i);
|
|
||||||
}
|
|
||||||
config.chain_lengths.push_back(num_s + 1);
|
|
||||||
config.chain_periods.push_back(10);
|
|
||||||
for (int i = 0; i < num_s + 1; i++) {
|
|
||||||
config.callback_runtimes.push_back(runtime);
|
|
||||||
}
|
|
||||||
config.chain_timer_control.push_back(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "node ids: " << std::endl;
|
|
||||||
for (size_t i = 0; i < config.callback_ids.size(); i++) {
|
|
||||||
for (size_t j = 0; j < config.callback_ids[i].size(); j++) {
|
|
||||||
std::cout << config.callback_ids[i][j] << " ";
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.num_groups = 0;
|
|
||||||
config.group_memberships = {}; // no groups
|
|
||||||
|
|
||||||
config.callback_executor_assignments = {};
|
|
||||||
// config.node_executor_assignments = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
||||||
config.parallel_mode = false;
|
|
||||||
config.cores = 1;
|
|
||||||
config.special_cases.push_back("2024ours_latency");
|
|
||||||
|
|
||||||
// sanity_check_config(config);
|
|
||||||
|
|
||||||
// from 35/65 to 100/0 in increments of 5
|
|
||||||
// for (int i = 0; i <= (100 - 35) / 5; i++)
|
|
||||||
// {
|
|
||||||
// for now, run with 100% ros
|
|
||||||
int const i = (100 - 35) / 5;
|
|
||||||
int const ros_budget = 35 + i * 5;
|
|
||||||
int const other_task_budget = 65 - i * 5;
|
|
||||||
std::cout << "ROS budget: " << ros_budget
|
|
||||||
<< " Other task budget: " << other_task_budget << std::endl;
|
|
||||||
// do other task in a separate thread
|
|
||||||
should_do_task.store(true);
|
|
||||||
|
|
||||||
// do ros task
|
|
||||||
std::string const file_name = "ros_" + std::to_string(ros_budget);
|
|
||||||
// ros_experiment(argc, argv, file_name);
|
|
||||||
std::thread ros_task(ros_experiment, node, file_name, config);
|
|
||||||
std::cout << "tasks started" << std::endl;
|
|
||||||
ros_task.join();
|
|
||||||
std::cout << "tasks joined" << std::endl;
|
|
||||||
|
|
||||||
// // run once
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,139 +0,0 @@
|
||||||
#include <cmath>
|
|
||||||
#include <chrono>
|
|
||||||
#include <string>
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
#include <rclcpp/rclcpp.hpp>
|
|
||||||
#include <std_msgs/msg/string.hpp>
|
|
||||||
#include "casestudy_tools/test_nodes.hpp"
|
|
||||||
#include "casestudy_tools/experiment.hpp"
|
|
||||||
#include "casestudy_tools/primes_workload.hpp"
|
|
||||||
#include "priority_executor/priority_executor.hpp"
|
|
||||||
#include "priority_executor/priority_memory_strategy.hpp"
|
|
||||||
|
|
||||||
// bool should_do_task = true;
|
|
||||||
std::atomic<bool> should_do_task(true);
|
|
||||||
|
|
||||||
void run_one_executor(
|
|
||||||
std::function<void(std::atomic<bool>&)> const& exec_fun,
|
|
||||||
int const idx) {
|
|
||||||
(void)idx;
|
|
||||||
// cpu_set_t cpuset;
|
|
||||||
// CPU_ZERO(&cpuset);
|
|
||||||
// CPU_SET(0, &cpuset);
|
|
||||||
// CPU_SET(1, &cpuset);
|
|
||||||
// CPU_SET(2, &cpuset);
|
|
||||||
// CPU_SET(3, &cpuset);
|
|
||||||
|
|
||||||
// int result = sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
|
|
||||||
// if (result != 0)
|
|
||||||
// {
|
|
||||||
// std::cout << "ros_experiment: sched_setaffinity failed: " << result << ": " << strerror(errno) << std::endl;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// set this process to second highest priority
|
|
||||||
struct sched_param param;
|
|
||||||
param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 2;
|
|
||||||
int const result = sched_setscheduler(0, SCHED_FIFO, ¶m);
|
|
||||||
|
|
||||||
if (result != 0) {
|
|
||||||
std::cerr << "ros_experiment: sched_setscheduler failed: " << result
|
|
||||||
<< ": " << strerror(errno) << std::endl;
|
|
||||||
std::cerr << "This operation requires root privileges. Please run the program with sufficient permissions."
|
|
||||||
<< std::endl;
|
|
||||||
#ifndef WIN_DOCKER_IS_BROKEN
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
#else
|
|
||||||
// Windows Docker workaround: just print the error and continue
|
|
||||||
std::cerr << "Continuing without setting scheduler, fuck Windows" << std::endl;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the process name to "ros_experiment" for easier identification during debugging or system monitoring.
|
|
||||||
prctl(PR_SET_NAME, "ros_experiment", 0, 0, 0);
|
|
||||||
exec_fun(should_do_task);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ros_experiment(
|
|
||||||
int const argc,
|
|
||||||
char** const argv,
|
|
||||||
std::string const& file_name,
|
|
||||||
ExperimentConfig config) {
|
|
||||||
|
|
||||||
rclcpp::init(argc, argv);
|
|
||||||
|
|
||||||
// we have to create a node to process any passed parameters
|
|
||||||
auto const node = rclcpp::Node::make_shared("experiment_parameters");
|
|
||||||
node->declare_parameter("executor_type", "edf");
|
|
||||||
|
|
||||||
std::string executor_type;
|
|
||||||
node->get_parameter("executor_type", executor_type);
|
|
||||||
std::cout << "using executor type: " << executor_type << std::endl;
|
|
||||||
|
|
||||||
config.executor_type = executor_type;
|
|
||||||
// Enable parallel mode for the experiment configuration.
|
|
||||||
// When set to true, this allows multiple executors to run concurrently,
|
|
||||||
// leveraging multi-threading to improve performance on multi-core systems.
|
|
||||||
config.parallel_mode = true;
|
|
||||||
config.nodes.push_back(node);
|
|
||||||
|
|
||||||
Experiment experiment(config);
|
|
||||||
// std::string result_log = experiment.run(should_do_task);
|
|
||||||
auto const exec_funs = experiment.getRunFunctions();
|
|
||||||
std::cout << "got " << exec_funs.size() << " executors" << std::endl;
|
|
||||||
|
|
||||||
std::vector<std::thread> exec_threads;
|
|
||||||
int i = 0;
|
|
||||||
experiment.resetTimers();
|
|
||||||
|
|
||||||
for (auto const& exec_fun : exec_funs) {
|
|
||||||
exec_threads.emplace_back(run_one_executor, exec_fun, i++);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& t : exec_threads) {
|
|
||||||
t.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string const outputname = "casestudy_example";
|
|
||||||
experiment.writeLogsToFile(file_name, outputname);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
// calibrate the dummy load for the current system
|
|
||||||
calibrate_dummy_load();
|
|
||||||
|
|
||||||
ExperimentConfig config;
|
|
||||||
config.chain_lengths = {2, 2};
|
|
||||||
config.callback_ids = {{0, 1}, {2, 3}};
|
|
||||||
config.callback_priorities = {1, 0, 3, 2};
|
|
||||||
config.chain_timer_control = {0, 1};
|
|
||||||
|
|
||||||
config.callback_runtimes = {10, 10, 10, 10};
|
|
||||||
// node 0 has a period of 80, and is the only timer
|
|
||||||
config.chain_periods = {100, 100};
|
|
||||||
config.callback_executor_assignments = {};
|
|
||||||
config.parallel_mode = true;
|
|
||||||
config.cores = 2;
|
|
||||||
|
|
||||||
sanity_check_config(config);
|
|
||||||
|
|
||||||
std::thread ros_task([&]() {
|
|
||||||
try {
|
|
||||||
ros_experiment(argc, argv, "cs_example", config);
|
|
||||||
} catch (std::exception const& e) {
|
|
||||||
std::cerr << "Exception in ros_experiment: " << e.what() << std::endl;
|
|
||||||
} catch (...) {
|
|
||||||
std::cerr << "Unknown exception in ros_experiment." << std::endl;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
std::cout << "tasks started" << std::endl;
|
|
||||||
ros_task.join();
|
|
||||||
std::cout << "tasks joined" << std::endl;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,189 +0,0 @@
|
||||||
#include "casestudy_tools/experiment.hpp"
|
|
||||||
#include "casestudy_tools/primes_workload.hpp"
|
|
||||||
|
|
||||||
#include "priority_executor/priority_executor.hpp"
|
|
||||||
#include "priority_executor/priority_memory_strategy.hpp"
|
|
||||||
|
|
||||||
#include <rclcpp/rclcpp.hpp>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
|
|
||||||
// bool should_do_task = true;
|
|
||||||
std::atomic<bool> should_do_task(true);
|
|
||||||
|
|
||||||
void run_one_executor(
|
|
||||||
std::function<void(std::atomic<bool>&)> const& exec_fun,
|
|
||||||
int const idx) {
|
|
||||||
(void)idx;
|
|
||||||
// cpu_set_t cpuset;
|
|
||||||
// CPU_ZERO(&cpuset);
|
|
||||||
// CPU_SET(0, &cpuset);
|
|
||||||
// CPU_SET(1, &cpuset);
|
|
||||||
// CPU_SET(2, &cpuset);
|
|
||||||
// CPU_SET(3, &cpuset);
|
|
||||||
|
|
||||||
// int result = sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
|
|
||||||
// if (result != 0)
|
|
||||||
// {
|
|
||||||
// std::cout << "ros_experiment: sched_setaffinity failed: " << result << ": " << strerror(errno) << std::endl;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// set this process to second highest priority
|
|
||||||
struct sched_param param;
|
|
||||||
param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 2;
|
|
||||||
int const result = sched_setscheduler(0, SCHED_FIFO, ¶m);
|
|
||||||
|
|
||||||
if (result != 0) {
|
|
||||||
std::cerr << "ros_experiment: sched_setscheduler failed: " << result
|
|
||||||
<< ": " << strerror(errno) << std::endl;
|
|
||||||
std::cerr << "This operation requires root privileges. Please run the program with sufficient permissions."
|
|
||||||
<< std::endl;
|
|
||||||
#ifndef WIN_DOCKER_IS_BROKEN
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
#else
|
|
||||||
// Windows Docker workaround: just print the error and continue
|
|
||||||
std::cerr << "Continuing without setting scheduler, fuck Windows" << std::endl;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the process name to "ros_experiment" for easier identification during debugging or system monitoring.
|
|
||||||
prctl(PR_SET_NAME, "ros_experiment", 0, 0, 0);
|
|
||||||
exec_fun(should_do_task);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ros_experiment(
|
|
||||||
int const argc,
|
|
||||||
char** const argv,
|
|
||||||
std::string const& file_name,
|
|
||||||
ExperimentConfig config)
|
|
||||||
{
|
|
||||||
rclcpp::init(argc, argv);
|
|
||||||
|
|
||||||
// we have to create a node to process any passed parameters
|
|
||||||
auto const node = rclcpp::Node::make_shared("experiment_parameters");
|
|
||||||
node->declare_parameter("executor_type", "edf");
|
|
||||||
|
|
||||||
std::string executor_type;
|
|
||||||
node->get_parameter("executor_type", executor_type);
|
|
||||||
std::cout << "using executor type: " << executor_type << std::endl;
|
|
||||||
|
|
||||||
config.executor_type = executor_type;
|
|
||||||
// Enable parallel mode for the experiment configuration.
|
|
||||||
// When set to true, this allows multiple executors to run concurrently,
|
|
||||||
// leveraging multi-threading to improve performance on multi-core systems.
|
|
||||||
config.parallel_mode = true;
|
|
||||||
config.nodes.push_back(node);
|
|
||||||
|
|
||||||
Experiment experiment(config);
|
|
||||||
// std::string result_log = experiment.run(should_do_task);
|
|
||||||
auto const exec_funs = experiment.getRunFunctions();
|
|
||||||
std::cout << "got " << exec_funs.size() << " executors" << std::endl;
|
|
||||||
|
|
||||||
std::vector<std::thread> exec_threads;
|
|
||||||
int i = 0;
|
|
||||||
experiment.resetTimers();
|
|
||||||
|
|
||||||
for (auto const& exec_fun : exec_funs) {
|
|
||||||
exec_threads.emplace_back(run_one_executor, exec_fun, i++);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& t : exec_threads) {
|
|
||||||
t.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string const outputname = "casestudy_wildfire_drone";
|
|
||||||
experiment.writeLogsToFile(file_name, outputname);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Create a PriorityMemoryStrategy instance
|
|
||||||
auto strat = std::make_shared<priority_executor::PriorityMemoryStrategy<>>();
|
|
||||||
|
|
||||||
// Atomic flags to control the chains
|
|
||||||
std::atomic<bool> fire_detection_running{true};
|
|
||||||
std::atomic<bool> image_processing_running{true};
|
|
||||||
std::atomic<bool> data_streaming_running{true};
|
|
||||||
std::atomic<bool> flight_control_running{true};
|
|
||||||
|
|
||||||
// Launch chains in separate threads
|
|
||||||
|
|
||||||
|
|
||||||
// Run for a fixed duration (e.g., 10 seconds)
|
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
|
||||||
|
|
||||||
// Stop all chains
|
|
||||||
fire_detection_running.store(false);
|
|
||||||
image_processing_running.store(false);
|
|
||||||
data_streaming_running.store(false);
|
|
||||||
flight_control_running.store(false);
|
|
||||||
|
|
||||||
// Join threads
|
|
||||||
fire_detection_thread.join();
|
|
||||||
image_processing_thread.join();
|
|
||||||
data_streaming_thread.join();
|
|
||||||
flight_control_thread.join();
|
|
||||||
|
|
||||||
std::cout << "All chains stopped. Exiting..." << std::endl;
|
|
||||||
*/
|
|
||||||
|
|
||||||
rclcpp::shutdown();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
// calibrate the dummy load for the current system
|
|
||||||
calibrate_dummy_load();
|
|
||||||
|
|
||||||
ExperimentConfig config;
|
|
||||||
// call back id: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
|
|
||||||
config.callback_priorities = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
||||||
// duration of the callback in ms
|
|
||||||
config.callback_runtimes = { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10};
|
|
||||||
|
|
||||||
// we have four chains where
|
|
||||||
// - the first is 1 + numOf(radiometric corrections) + 1 long
|
|
||||||
// - the second is 1 + numOf(radiometric corrections) + numOf(geometric corrections) + 1 long
|
|
||||||
// - the third is 3 long
|
|
||||||
// - the fourth is 4 long
|
|
||||||
config.callback_ids = {
|
|
||||||
{1, 2, 3, 0},
|
|
||||||
{1, 2, 3, 4, 5, 6},
|
|
||||||
{1, 2, 3, 4, 5, 6, 9, 7, 8},
|
|
||||||
{9, 12, 13, 10, 11}
|
|
||||||
};
|
|
||||||
config.chain_lengths = {4, 6, 9, 5};
|
|
||||||
|
|
||||||
// period of the timer of the starting node in ms - thus basically sensor frequency
|
|
||||||
config.chain_periods = {100, 100, 100, 100};
|
|
||||||
config.chain_timer_control = {0, 0, 0, 0};
|
|
||||||
|
|
||||||
// TODO: ?
|
|
||||||
config.callback_executor_assignments = {};
|
|
||||||
config.parallel_mode = false;
|
|
||||||
config.cores = 1;
|
|
||||||
|
|
||||||
// TODO: you can add nodes to the config - what does it do? do we want that?
|
|
||||||
|
|
||||||
sanity_check_config(config);
|
|
||||||
|
|
||||||
std::thread ros_task([&]() {
|
|
||||||
try {
|
|
||||||
ros_experiment(argc, argv, "cs_example", config);
|
|
||||||
} catch (std::exception const& e) {
|
|
||||||
std::cerr << "Exception in ros_experiment: " << e.what() << std::endl;
|
|
||||||
} catch (...) {
|
|
||||||
std::cerr << "Unknown exception in ros_experiment." << std::endl;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
std::cout << "tasks started" << std::endl;
|
|
||||||
ros_task.join();
|
|
||||||
std::cout << "tasks joined" << std::endl;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,175 +0,0 @@
|
||||||
#include <cmath>
|
|
||||||
#include <chrono>
|
|
||||||
#include <string>
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
#include <rclcpp/rclcpp.hpp>
|
|
||||||
#include <std_msgs/msg/string.hpp>
|
|
||||||
#include "casestudy_tools/test_nodes.hpp"
|
|
||||||
#include "casestudy_tools/experiment.hpp"
|
|
||||||
#include "casestudy_tools/primes_workload.hpp"
|
|
||||||
#include "priority_executor/priority_executor.hpp"
|
|
||||||
#include "priority_executor/priority_memory_strategy.hpp"
|
|
||||||
|
|
||||||
std::atomic<bool> should_do_task(true);
|
|
||||||
|
|
||||||
// Global performance monitoring configuration
|
|
||||||
constexpr size_t MONITOR_BUFFER_SIZE = 10000;
|
|
||||||
constexpr size_t MONITOR_AUTO_DUMP_THRESHOLD = 5000;
|
|
||||||
const std::string MONITOR_OUTPUT_DIR = "performance_logs/monitored_example";
|
|
||||||
|
|
||||||
void configure_executor_monitoring(std::shared_ptr<priority_executor::TimedExecutor> executor,
|
|
||||||
const std::string& name) {
|
|
||||||
executor->setMonitoringOptions(
|
|
||||||
MONITOR_BUFFER_SIZE,
|
|
||||||
MONITOR_AUTO_DUMP_THRESHOLD,
|
|
||||||
MONITOR_OUTPUT_DIR + "/" + name
|
|
||||||
);
|
|
||||||
|
|
||||||
// Enable monitoring by default
|
|
||||||
executor->enableMonitoring(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run_one_executor(
|
|
||||||
std::function<void(std::atomic<bool>&)> const& exec_fun,
|
|
||||||
int const idx) {
|
|
||||||
// Set up process scheduling
|
|
||||||
struct sched_param param;
|
|
||||||
param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 2;
|
|
||||||
int const result = sched_setscheduler(0, SCHED_FIFO, ¶m);
|
|
||||||
|
|
||||||
if (result != 0) {
|
|
||||||
std::cerr << "ros_experiment: sched_setscheduler failed: " << result
|
|
||||||
<< ": " << strerror(errno) << std::endl;
|
|
||||||
std::cerr << "This operation requires root privileges. Please run the program with sufficient permissions."
|
|
||||||
<< std::endl;
|
|
||||||
#ifndef WIN_DOCKER_IS_BROKEN
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
#else
|
|
||||||
std::cerr << "Continuing without setting scheduler, fuck Windows" << std::endl;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
prctl(PR_SET_NAME, "ros_experiment", 0, 0, 0);
|
|
||||||
exec_fun(should_do_task);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ros_experiment(
|
|
||||||
int const argc,
|
|
||||||
char** const argv,
|
|
||||||
std::string const& file_name,
|
|
||||||
ExperimentConfig config) {
|
|
||||||
|
|
||||||
rclcpp::init(argc, argv);
|
|
||||||
|
|
||||||
auto const node = rclcpp::Node::make_shared("experiment_parameters");
|
|
||||||
node->declare_parameter("executor_type", "edf");
|
|
||||||
|
|
||||||
std::string executor_type;
|
|
||||||
node->get_parameter("executor_type", executor_type);
|
|
||||||
std::cout << "using executor type: " << executor_type << std::endl;
|
|
||||||
|
|
||||||
config.executor_type = executor_type;
|
|
||||||
config.parallel_mode = true;
|
|
||||||
config.nodes.push_back(node);
|
|
||||||
|
|
||||||
Experiment experiment(config);
|
|
||||||
auto const exec_funs = experiment.getRunFunctions();
|
|
||||||
std::cout << "got " << exec_funs.size() << " executors" << std::endl;
|
|
||||||
|
|
||||||
// Configure monitoring for each executor
|
|
||||||
int executor_id = 0;
|
|
||||||
std::vector<std::shared_ptr<priority_executor::TimedExecutor>> monitored_executors;
|
|
||||||
for (const auto& executor : experiment.getExecutors()) {
|
|
||||||
if (auto timed_exec = std::dynamic_pointer_cast<priority_executor::TimedExecutor>(executor.executor)) {
|
|
||||||
std::string exec_name = "executor_" + std::to_string(executor_id++);
|
|
||||||
configure_executor_monitoring(timed_exec, exec_name);
|
|
||||||
monitored_executors.push_back(timed_exec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::thread> exec_threads;
|
|
||||||
int i = 0;
|
|
||||||
experiment.resetTimers();
|
|
||||||
|
|
||||||
for (auto const& exec_fun : exec_funs) {
|
|
||||||
exec_threads.emplace_back(run_one_executor, exec_fun, i++);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& t : exec_threads) {
|
|
||||||
t.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write performance reports for each executor
|
|
||||||
std::ofstream report_file(MONITOR_OUTPUT_DIR + "/performance_report.txt");
|
|
||||||
if (report_file.is_open()) {
|
|
||||||
report_file << "===========================\n";
|
|
||||||
report_file << " Performance Monitor Report\n";
|
|
||||||
report_file << "===========================\n\n";
|
|
||||||
|
|
||||||
for (const auto& executor : monitored_executors) {
|
|
||||||
report_file << "Executor: " << executor->getExecutorName()
|
|
||||||
<< " (ID: " << executor->getExecutorId() << ")\n";
|
|
||||||
report_file << "----------------------------------------\n";
|
|
||||||
report_file << executor->getCallbackExecutionReport() << "\n\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
report_file.close();
|
|
||||||
std::cout << "Performance report written to " << MONITOR_OUTPUT_DIR
|
|
||||||
<< "/performance_report.txt" << std::endl;
|
|
||||||
} else {
|
|
||||||
std::cerr << "Failed to open performance report file" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure all performance logs are written before experiment logs
|
|
||||||
auto& monitor = priority_executor::PerformanceMonitor::getInstance();
|
|
||||||
monitor.dumpToFile("final_dump.json");
|
|
||||||
|
|
||||||
std::string const outputname = "monitored_example";
|
|
||||||
experiment.writeLogsToFile(file_name, outputname);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
// Create the performance logs directory
|
|
||||||
std::filesystem::create_directories(MONITOR_OUTPUT_DIR);
|
|
||||||
|
|
||||||
// calibrate the dummy load for the current system
|
|
||||||
calibrate_dummy_load();
|
|
||||||
|
|
||||||
ExperimentConfig config;
|
|
||||||
// Set up two chains of callbacks to demonstrate monitoring
|
|
||||||
config.chain_lengths = {2, 2};
|
|
||||||
config.callback_ids = {{0, 1}, {2, 3}};
|
|
||||||
config.callback_priorities = {1, 0, 3, 2};
|
|
||||||
config.chain_timer_control = {0, 1};
|
|
||||||
|
|
||||||
// Set different runtimes to see varying processing times in the logs
|
|
||||||
config.callback_runtimes = {10, 20, 15, 25};
|
|
||||||
config.chain_periods = {100, 100};
|
|
||||||
config.callback_executor_assignments = {};
|
|
||||||
config.parallel_mode = true;
|
|
||||||
config.cores = 2;
|
|
||||||
|
|
||||||
sanity_check_config(config);
|
|
||||||
|
|
||||||
std::thread ros_task([&]() {
|
|
||||||
try {
|
|
||||||
ros_experiment(argc, argv, "monitored_example", config);
|
|
||||||
} catch (std::exception const& e) {
|
|
||||||
std::cerr << "Exception in ros_experiment: " << e.what() << std::endl;
|
|
||||||
} catch (...) {
|
|
||||||
std::cerr << "Unknown exception in ros_experiment." << std::endl;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
std::cout << "tasks started" << std::endl;
|
|
||||||
ros_task.join();
|
|
||||||
std::cout << "tasks joined" << std::endl;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.8)
|
|
||||||
project(casestudy_tools VERSION 0.1.0)
|
|
||||||
|
|
||||||
# Set C++ standards
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
|
|
||||||
# Compiler options
|
|
||||||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
||||||
add_compile_options(-Wall -Wextra -Wpedantic)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Find dependencies
|
|
||||||
find_package(ament_cmake REQUIRED)
|
|
||||||
find_package(tracetools REQUIRED)
|
|
||||||
find_package(rclcpp REQUIRED)
|
|
||||||
find_package(priority_executor REQUIRED)
|
|
||||||
find_package(simple_timer REQUIRED)
|
|
||||||
|
|
||||||
# Library targets
|
|
||||||
add_library(primes_workload
|
|
||||||
src/primes_workload.cpp
|
|
||||||
)
|
|
||||||
target_include_directories(primes_workload PUBLIC
|
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
||||||
$<INSTALL_INTERFACE:include>
|
|
||||||
)
|
|
||||||
ament_target_dependencies(primes_workload
|
|
||||||
tracetools
|
|
||||||
simple_timer
|
|
||||||
rclcpp
|
|
||||||
priority_executor
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(test_nodes src/test_nodes.cpp)
|
|
||||||
target_include_directories(test_nodes PUBLIC
|
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
||||||
$<INSTALL_INTERFACE:include>
|
|
||||||
)
|
|
||||||
target_link_libraries(test_nodes primes_workload)
|
|
||||||
ament_target_dependencies(test_nodes
|
|
||||||
tracetools
|
|
||||||
rclcpp
|
|
||||||
simple_timer
|
|
||||||
)
|
|
||||||
add_library(experiment_host
|
|
||||||
src/experiment.cpp
|
|
||||||
)
|
|
||||||
target_include_directories(experiment_host PUBLIC
|
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
||||||
$<INSTALL_INTERFACE:include>
|
|
||||||
)
|
|
||||||
target_link_libraries(experiment_host
|
|
||||||
primes_workload
|
|
||||||
test_nodes
|
|
||||||
)
|
|
||||||
ament_target_dependencies(experiment_host
|
|
||||||
tracetools
|
|
||||||
rclcpp
|
|
||||||
simple_timer
|
|
||||||
priority_executor
|
|
||||||
)
|
|
||||||
|
|
||||||
# Testing
|
|
||||||
if(BUILD_TESTING)
|
|
||||||
find_package(ament_lint_auto REQUIRED)
|
|
||||||
ament_lint_auto_find_test_dependencies()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
install(
|
|
||||||
DIRECTORY include/
|
|
||||||
DESTINATION include
|
|
||||||
)
|
|
||||||
|
|
||||||
install(
|
|
||||||
TARGETS primes_workload test_nodes experiment_host
|
|
||||||
EXPORT export_${PROJECT_NAME}
|
|
||||||
ARCHIVE DESTINATION lib
|
|
||||||
LIBRARY DESTINATION lib
|
|
||||||
RUNTIME DESTINATION bin
|
|
||||||
INCLUDES DESTINATION include
|
|
||||||
)
|
|
||||||
|
|
||||||
# Export and package configuration
|
|
||||||
ament_export_include_directories(include)
|
|
||||||
# ament_export_libraries(primes_workload test_nodes experiment_host)
|
|
||||||
ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)
|
|
||||||
ament_export_dependencies(rclcpp priority_executor simple_timer)
|
|
||||||
ament_package()
|
|
|
@ -1,149 +0,0 @@
|
||||||
#ifndef RTIS_EXPERIMENT_HOST
|
|
||||||
#define RTIS_EXPERIMENT_HOST
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include "priority_executor/priority_executor.hpp"
|
|
||||||
#include "casestudy_tools/test_nodes.hpp"
|
|
||||||
#include "simple_timer/rt-sched.hpp"
|
|
||||||
|
|
||||||
struct ExperimentConfig
|
|
||||||
{
|
|
||||||
std::vector<uint32_t> chain_lengths;
|
|
||||||
std::vector<std::vector<uint32_t>> callback_ids;
|
|
||||||
|
|
||||||
// duration of the callback in ms
|
|
||||||
std::vector<double> callback_runtimes;
|
|
||||||
|
|
||||||
// used for picas or in edf as tie breaker
|
|
||||||
std::vector<uint32_t> callback_priorities;
|
|
||||||
|
|
||||||
// period of the timer of the starting node in ms
|
|
||||||
std::vector<int> chain_periods;
|
|
||||||
|
|
||||||
// for each chain, the index of the timer node - TODO: why would I ever want this to be non 0?
|
|
||||||
std::vector<int> chain_timer_control;
|
|
||||||
|
|
||||||
uint num_groups = 0;
|
|
||||||
std::vector<int> group_memberships;
|
|
||||||
|
|
||||||
// TODO: change to enum
|
|
||||||
// can be "default", "picas", "edf"
|
|
||||||
std::string executor_type = "default";
|
|
||||||
|
|
||||||
// if this is empty, then the nodes will be assigned to a single executor
|
|
||||||
std::vector<uint32_t> callback_executor_assignments;
|
|
||||||
|
|
||||||
// used in single-threaded mode to assign executors to cores
|
|
||||||
std::vector<uint32_t> executor_to_cpu_assignments;
|
|
||||||
|
|
||||||
bool parallel_mode = false;
|
|
||||||
size_t cores = 2;
|
|
||||||
|
|
||||||
// nodes to host the callbacks. In parallel (one executor using multiple threads) mode,
|
|
||||||
// there is one node and one executor. The node is provided by the experiment provider.
|
|
||||||
// In singlethread (each executor uses one thread), there is one node per executor.
|
|
||||||
// Additional nodes may be created by the experiment host.
|
|
||||||
std::vector<rclcpp::Node::SharedPtr> nodes;
|
|
||||||
std::vector<std::string> special_cases;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct experiment_executor
|
|
||||||
{
|
|
||||||
std::shared_ptr<priority_executor::TimedExecutor> executor;
|
|
||||||
std::shared_ptr<priority_executor::PriorityMemoryStrategy<>> strat;
|
|
||||||
std::shared_ptr<rclcpp::Executor> default_executor;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Experiment
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit Experiment(ExperimentConfig config);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Run the experiment in the current thread
|
|
||||||
* @param should_do_task a flag that can be set to false to stop the experiment.
|
|
||||||
* The flag will be set to false when the experiment is complete.
|
|
||||||
* @return std::string containing experiment results
|
|
||||||
*/
|
|
||||||
std::string run(std::atomic<bool> &should_do_task);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the functions required to run the experiment
|
|
||||||
* @return vector of functions. Each function will run a different executor.
|
|
||||||
* It's up to the caller to run these functions in different threads.
|
|
||||||
* The caller may set the thread properties (e.g. priority) before running the function.
|
|
||||||
* Each function accepts a flag that can be set to false to stop the experiment.
|
|
||||||
* The flag will be set to false when the experiment is complete.
|
|
||||||
*/
|
|
||||||
std::vector<std::function<void(std::atomic<bool> &)>> getRunFunctions();
|
|
||||||
|
|
||||||
static size_t numExecutorsRequired(ExperimentConfig const &config);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the executor (and strategy) for a given executor index
|
|
||||||
* @param executor_idx The index of the executor (defaults to 0)
|
|
||||||
* @return experiment_executor
|
|
||||||
*/
|
|
||||||
experiment_executor getExecutor(int executor_idx = 0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the executors
|
|
||||||
* @return experiment_executors
|
|
||||||
*/
|
|
||||||
std::vector<experiment_executor> getExecutors();
|
|
||||||
|
|
||||||
node_time_logger getInternalLogger();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Collect logs from the executors and optionally add logs from passed in loggers
|
|
||||||
* @param loggers Optional array of loggers to add to the logs
|
|
||||||
* @return std::string containing the combined logs
|
|
||||||
*/
|
|
||||||
std::string buildLogs(std::vector<node_time_logger> loggers = {});
|
|
||||||
|
|
||||||
void resetTimers();
|
|
||||||
void writeLogsToFile(std::string const &filename, std::string const &experiment_name);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ExperimentConfig config;
|
|
||||||
std::vector<experiment_executor> executors;
|
|
||||||
|
|
||||||
// Chain-indexed vector of deques of deadlines
|
|
||||||
std::vector<std::deque<uint64_t> *> chain_deadlines_deque;
|
|
||||||
// Maps node_id to node
|
|
||||||
std::map<uint32_t, std::shared_ptr<CaseStudyNode>> nodes;
|
|
||||||
// Maps chain_id to timer
|
|
||||||
std::map<uint32_t, std::shared_ptr<rclcpp::TimerBase>> chain_timer_handles;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Create the executors using the type and amount set in the config
|
|
||||||
*/
|
|
||||||
void createExecutors();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Create a single executor using the type set in the config
|
|
||||||
* @param executor_num The executor number to create
|
|
||||||
* @return experiment_executor
|
|
||||||
*/
|
|
||||||
experiment_executor createSingleExecutor(uint executor_num = 0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the executor for a given node id
|
|
||||||
* @param node_id If node_executor_assignments is empty, this parameter is ignored
|
|
||||||
* and the first executor is returned
|
|
||||||
* @return experiment_executor
|
|
||||||
*/
|
|
||||||
experiment_executor getExecutorForNode(int node_id = 0);
|
|
||||||
|
|
||||||
void createNodesAndAssignProperties();
|
|
||||||
void setInitialDeadlines();
|
|
||||||
|
|
||||||
node_time_logger _logger;
|
|
||||||
};
|
|
||||||
|
|
||||||
int do_other_task(double work_time, uint period, std::atomic<bool> &should_do_task);
|
|
||||||
void sanity_check_config(ExperimentConfig const &config);
|
|
||||||
|
|
||||||
#endif // RTIS_EXPERIMENT_HOST
|
|
|
@ -1,44 +0,0 @@
|
||||||
#ifndef RTIS_PRIMES_WORKLOAD
|
|
||||||
#define RTIS_PRIMES_WORKLOAD
|
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#include <time.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
|
|
||||||
// Parameters for the dummy load calibration
|
|
||||||
constexpr int TARGET_DURATION_MS = 100;
|
|
||||||
constexpr int MAX_ITERATIONS = 100;
|
|
||||||
constexpr int ACCEPTABLE_MARGIN_US = 500;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Dummy load calibration namespace.
|
|
||||||
* This namespace contains functions and variables for calibrating the dummy load.
|
|
||||||
* It uses an atomic integer to store the calibration factor.
|
|
||||||
* The calibration factor is used to adjust the workload of the dummy load function.
|
|
||||||
*/
|
|
||||||
namespace DummyLoadCalibration
|
|
||||||
{
|
|
||||||
inline std::atomic<int> calibration{1};
|
|
||||||
|
|
||||||
inline void setCalibration(int const calib);
|
|
||||||
|
|
||||||
inline int getCalibration();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Dummy load function to simulate a workload.
|
|
||||||
* This function performs a busy wait for a specified duration.
|
|
||||||
* It is used to calibrate the dummy load for the current system.
|
|
||||||
*/
|
|
||||||
void calibrate_dummy_load();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Dummy load function to simulate a workload.
|
|
||||||
* This function performs a busy wait for a specified duration.
|
|
||||||
* It is used to calibrate the dummy load for the current system.
|
|
||||||
*/
|
|
||||||
void dummy_load(int const load_ms);
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,66 +0,0 @@
|
||||||
#ifndef RTIS_TEST_NODES
|
|
||||||
#define RTIS_TEST_NODES
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include "rclcpp/rclcpp.hpp"
|
|
||||||
#include "std_msgs/msg/string.hpp"
|
|
||||||
#include "std_msgs/msg/int32.hpp"
|
|
||||||
#include "simple_timer/rt-sched.hpp"
|
|
||||||
|
|
||||||
class CaseStudyNode {
|
|
||||||
public:
|
|
||||||
node_time_logger logger;
|
|
||||||
// inherit constructor
|
|
||||||
CaseStudyNode(std::string const &publish_topic, rclcpp::Node::SharedPtr const &node) {
|
|
||||||
this->name = publish_topic;
|
|
||||||
this->node = node;
|
|
||||||
// seed random number generator
|
|
||||||
srand(time(nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool use_random_runtime = false;
|
|
||||||
double normal_runtime_scale = 0.8;
|
|
||||||
double over_runtime_scale = 1.2;
|
|
||||||
double runtime_over_chance = 0.1;
|
|
||||||
std::string get_name();
|
|
||||||
rclcpp::CallbackGroup::SharedPtr callback_group_;
|
|
||||||
|
|
||||||
private:
|
|
||||||
rclcpp::Node::SharedPtr node;
|
|
||||||
std::string name;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define COUNT_MAX 500
|
|
||||||
|
|
||||||
class PublisherNode : public CaseStudyNode {
|
|
||||||
public:
|
|
||||||
PublisherNode(std::string const &publish_topic, double runtime, int period, int chain,
|
|
||||||
rclcpp::Node::SharedPtr const &node,
|
|
||||||
rclcpp::CallbackGroup::SharedPtr const &callback_group = nullptr);
|
|
||||||
rclcpp::TimerBase::SharedPtr timer_;
|
|
||||||
int count_max_ = COUNT_MAX;
|
|
||||||
|
|
||||||
private:
|
|
||||||
rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr pub_;
|
|
||||||
double runtime_;
|
|
||||||
int period_;
|
|
||||||
int chain_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class WorkerNode : public CaseStudyNode {
|
|
||||||
public:
|
|
||||||
WorkerNode(std::string const &subscribe_topic, std::string const &publish_topic,
|
|
||||||
double runtime, int period, int chain,
|
|
||||||
rclcpp::Node::SharedPtr const &node,
|
|
||||||
rclcpp::CallbackGroup::SharedPtr const &callback_group = nullptr);
|
|
||||||
rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr sub_;
|
|
||||||
int count_max_ = COUNT_MAX;
|
|
||||||
|
|
||||||
private:
|
|
||||||
double runtime_;
|
|
||||||
int period_;
|
|
||||||
int chain_;
|
|
||||||
rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr pub_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // RTIS_TEST_NODES
|
|
|
@ -1,25 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
|
||||||
<package format="3">
|
|
||||||
<name>casestudy_tools</name>
|
|
||||||
<version>0.1.0</version>
|
|
||||||
<description>
|
|
||||||
ROS 2 package providing utility tools and common functionality for dynamic executor case studies
|
|
||||||
</description>
|
|
||||||
<maintainer email="kurt4wilson@gmail.com">kurt</maintainer>
|
|
||||||
<license>Apache License 2.0</license>
|
|
||||||
|
|
||||||
<buildtool_depend>ament_cmake</buildtool_depend>
|
|
||||||
|
|
||||||
<depend>tracetools</depend>
|
|
||||||
<depend>rclcpp</depend>
|
|
||||||
<depend>priority_executor</depend>
|
|
||||||
<depend>simple_timer</depend>
|
|
||||||
|
|
||||||
<test_depend>ament_lint_auto</test_depend>
|
|
||||||
<test_depend>ament_lint_common</test_depend>
|
|
||||||
|
|
||||||
<export>
|
|
||||||
<build_type>ament_cmake</build_type>
|
|
||||||
</export>
|
|
||||||
</package>
|
|
|
@ -1,614 +0,0 @@
|
||||||
#include "casestudy_tools/experiment.hpp"
|
|
||||||
#include "casestudy_tools/test_nodes.hpp"
|
|
||||||
#include "casestudy_tools/primes_workload.hpp"
|
|
||||||
|
|
||||||
#include "priority_executor/priority_executor.hpp"
|
|
||||||
#include "priority_executor/priority_memory_strategy.hpp"
|
|
||||||
#include "priority_executor/multithread_priority_executor.hpp"
|
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
#include <fstream>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <iostream>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
|
|
||||||
|
|
||||||
Experiment::Experiment(ExperimentConfig config)
|
|
||||||
: config(std::move(config)) {
|
|
||||||
_logger = create_logger();
|
|
||||||
}
|
|
||||||
|
|
||||||
void sanity_check_config(ExperimentConfig const &config) {
|
|
||||||
// make sure chain_lengths and node_ids are the same size
|
|
||||||
if (config.chain_lengths.size() != config.callback_ids.size()) {
|
|
||||||
std::cout << "ros_experiment: chain_lengths.size()= "
|
|
||||||
<< config.chain_lengths.size()
|
|
||||||
<< " != node_ids.size()= " << config.callback_ids.size() << std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure each chain_lengths is the same size as the corresponding node_ids
|
|
||||||
for (uint32_t i = 0; i < config.chain_lengths.size(); i++) {
|
|
||||||
if (config.chain_lengths[i] != config.callback_ids[i].size()) {
|
|
||||||
std::cout << "ros_experiment: chain_lengths[" << i
|
|
||||||
<< "]= " << config.chain_lengths[i] << " != node_ids[" << i
|
|
||||||
<< "].size()= " << config.callback_ids[i].size() << std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure chain_timer_control is the same size as chain_lengths
|
|
||||||
if (config.chain_timer_control.size() != config.chain_lengths.size()) {
|
|
||||||
std::cout << "ros_experiment: chain_timer_control.size()= "
|
|
||||||
<< config.chain_timer_control.size()
|
|
||||||
<< " != chain_lengths.size()= " << config.chain_lengths.size()
|
|
||||||
<< std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<int> all_node_ids;
|
|
||||||
for (auto const &chain : config.callback_ids) {
|
|
||||||
for (auto const &node_id : chain) {
|
|
||||||
all_node_ids.insert(node_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we have the right number of node_priorities and node_runtimes
|
|
||||||
if (all_node_ids.size() != config.callback_priorities.size()) {
|
|
||||||
std::cout << "ros_experiment: all_node_ids.size()= " << all_node_ids.size()
|
|
||||||
<< " != node_priorities.size()= " << config.callback_priorities.size()
|
|
||||||
<< std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (all_node_ids.size() != config.callback_runtimes.size()) {
|
|
||||||
std::cout << "ros_experiment: all_node_ids.size()= " << all_node_ids.size()
|
|
||||||
<< " != node_runtimes.size()= " << config.callback_runtimes.size()
|
|
||||||
<< std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Experiment::run(std::atomic<bool>& should_do_task) {
|
|
||||||
// TODO: split into setup and run, so that run can be re-used in
|
|
||||||
// Experiment::getRunFunctions
|
|
||||||
if (!config.callback_executor_assignments.empty()
|
|
||||||
|| numExecutorsRequired(config) != 1) {
|
|
||||||
std::cerr << "called Experiment::run with non-empty node_executor_assignments"
|
|
||||||
<< std::endl;
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
createExecutors();
|
|
||||||
createNodesAndAssignProperties();
|
|
||||||
setInitialDeadlines();
|
|
||||||
resetTimers();
|
|
||||||
|
|
||||||
// In this function (run), there is only one executor, so retrieve it now
|
|
||||||
experiment_executor executor = getExecutorForNode();
|
|
||||||
if (config.executor_type == "edf" || config.executor_type == "picas") {
|
|
||||||
executor.executor->spin();
|
|
||||||
} else if (config.executor_type == "default") {
|
|
||||||
executor.default_executor->spin();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "spin done" << std::endl;
|
|
||||||
should_do_task.store(false);
|
|
||||||
return buildLogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::function<void(std::atomic<bool>&)>>
|
|
||||||
Experiment::getRunFunctions() {
|
|
||||||
// Do pre-run setup
|
|
||||||
createExecutors();
|
|
||||||
createNodesAndAssignProperties();
|
|
||||||
|
|
||||||
// Create a function for each executor
|
|
||||||
std::vector<std::function<void(std::atomic<bool>&)>> run_functions;
|
|
||||||
for (size_t i = 0; i < numExecutorsRequired(config); i++) {
|
|
||||||
experiment_executor const executor = executors[i];
|
|
||||||
run_functions.emplace_back(
|
|
||||||
[this, executor](std::atomic<bool>& should_do_task) {
|
|
||||||
if (config.executor_type == "edf" || config.executor_type == "picas") {
|
|
||||||
executor.executor->spin();
|
|
||||||
} else if (config.executor_type == "default") {
|
|
||||||
executor.default_executor->spin();
|
|
||||||
}
|
|
||||||
std::cout << "spin done" << std::endl;
|
|
||||||
should_do_task.store(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do these last since they're time-sensitive
|
|
||||||
setInitialDeadlines();
|
|
||||||
return run_functions;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Experiment::numExecutorsRequired(ExperimentConfig const &config) {
|
|
||||||
if (config.callback_executor_assignments.empty()) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<int> unique_executors;
|
|
||||||
for (auto const &assignment : config.callback_executor_assignments) {
|
|
||||||
unique_executors.insert(assignment);
|
|
||||||
}
|
|
||||||
return unique_executors.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
experiment_executor Experiment::getExecutor(int const executor_idx) {
|
|
||||||
return executors[executor_idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<experiment_executor> Experiment::getExecutors() {
|
|
||||||
return executors;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Experiment::createExecutors() {
|
|
||||||
if (config.callback_executor_assignments.empty()) {
|
|
||||||
// Create a single executor
|
|
||||||
executors.push_back(createSingleExecutor(0));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check configuration validity
|
|
||||||
// depending on config.executor_to_cpu_assignments and config.parallel_mode,
|
|
||||||
// we may need to create the host nodes
|
|
||||||
if (!config.executor_to_cpu_assignments.empty() && config.parallel_mode) {
|
|
||||||
std::cerr << "executor_to_cpu_assignments not supported for parallel mode"
|
|
||||||
<< std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.executor_to_cpu_assignments.empty()
|
|
||||||
&& config.executor_to_cpu_assignments.size() != numExecutorsRequired(config)) {
|
|
||||||
std::cerr << "executor_to_cpu_assignments.size() != numExecutorsRequired(config)"
|
|
||||||
<< std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the required host nodes - re-use any existing nodes (by not starting at 0)
|
|
||||||
for (uint i = config.nodes.size(); i < config.executor_to_cpu_assignments.size(); i++) {
|
|
||||||
// create a node for each executor
|
|
||||||
auto const node = std::make_shared<rclcpp::Node>("node_" + std::to_string(i));
|
|
||||||
config.nodes.push_back(node);
|
|
||||||
}
|
|
||||||
std::cout << "created " << config.nodes.size() << " nodes" << std::endl;
|
|
||||||
|
|
||||||
int const num_executors = numExecutorsRequired(config);
|
|
||||||
std::cout << "creating " << num_executors << " executors" << std::endl;
|
|
||||||
for (int i = 0; i < num_executors; i++) {
|
|
||||||
executors.push_back(createSingleExecutor(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
experiment_executor Experiment::createSingleExecutor(uint const executor_num) {
|
|
||||||
experiment_executor executor = {nullptr, nullptr, nullptr};
|
|
||||||
|
|
||||||
if (config.executor_type == "edf" || config.executor_type == "picas") {
|
|
||||||
executor.strat = std::make_shared<priority_executor::PriorityMemoryStrategy<>>();
|
|
||||||
rclcpp::ExecutorOptions options;
|
|
||||||
options.memory_strategy = executor.strat;
|
|
||||||
|
|
||||||
if (config.parallel_mode) {
|
|
||||||
executor.executor = std::make_shared<priority_executor::MultithreadTimedExecutor>(
|
|
||||||
options, "priority_executor", config.cores);
|
|
||||||
} else {
|
|
||||||
executor.executor = std::make_shared<priority_executor::TimedExecutor>(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
executor.executor->prio_memory_strategy_ = executor.strat;
|
|
||||||
|
|
||||||
if (config.callback_executor_assignments.empty()) {
|
|
||||||
// Add all nodes to the executor
|
|
||||||
for (auto const &node : config.nodes) {
|
|
||||||
executor.executor->add_node(node);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add only this executor's nodes
|
|
||||||
executor.executor->add_node(config.nodes[executor_num]);
|
|
||||||
}
|
|
||||||
} else if (config.executor_type == "default") {
|
|
||||||
if (config.parallel_mode) {
|
|
||||||
// executor.default_executor =
|
|
||||||
// std::make_shared<ROSDefaultMultithreadedExecutor>(rclcpp::ExecutorOptions(),
|
|
||||||
// config.cores);
|
|
||||||
executor.default_executor = std::make_shared<rclcpp::executors::MultiThreadedExecutor>(
|
|
||||||
rclcpp::ExecutorOptions(), config.cores);
|
|
||||||
} else {
|
|
||||||
executor.default_executor = std::make_shared<priority_executor::ROSDefaultExecutor>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.callback_executor_assignments.empty()) {
|
|
||||||
// Add all nodes to the executor
|
|
||||||
for (auto const &node : config.nodes) {
|
|
||||||
executor.default_executor->add_node(node);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add only this executor's nodes
|
|
||||||
executor.default_executor->add_node(config.nodes[executor_num]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RCLCPP_ERROR(node->get_logger(), "Unknown executor type: %s",
|
|
||||||
// executor_type.c_str());
|
|
||||||
return executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
experiment_executor Experiment::getExecutorForNode(int const node_id) {
|
|
||||||
if (config.callback_executor_assignments.empty()) {
|
|
||||||
return executors[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
int const executor_idx = config.callback_executor_assignments[node_id];
|
|
||||||
std::cout << "node " << node_id << " assigned to executor " << executor_idx
|
|
||||||
<< std::endl;
|
|
||||||
return executors[executor_idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Experiment::createNodesAndAssignProperties() {
|
|
||||||
// BEGIN SPECIAL CASES
|
|
||||||
std::vector<std::vector<std::shared_ptr<CaseStudyNode>>> all_nodes;
|
|
||||||
rclcpp::CallbackGroup::SharedPtr cb_group0;
|
|
||||||
// LEGACY GROUP BEHAVIOR
|
|
||||||
bool jiang2022_cb = false;
|
|
||||||
bool jiang2022_cs2 = false;
|
|
||||||
// one-to-many test. TODO: add generic api for multiple one->many
|
|
||||||
bool ours2024_latency = false;
|
|
||||||
for (auto &special_case : config.special_cases) {
|
|
||||||
if (special_case == "jiang2022_cb") {
|
|
||||||
jiang2022_cb = true;
|
|
||||||
} else if (special_case == "2022jiang2") {
|
|
||||||
jiang2022_cs2 = true;
|
|
||||||
} else if (special_case == "2024ours_latency") {
|
|
||||||
std::cout << "using 2024ours_latency special case" << std::endl;
|
|
||||||
ours2024_latency = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (jiang2022_cb || jiang2022_cs2) {
|
|
||||||
// cb_group0 =
|
|
||||||
// config.node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
|
|
||||||
}
|
|
||||||
// END SPECIAL CASES
|
|
||||||
|
|
||||||
std::vector<rclcpp::CallbackGroup::SharedPtr> cb_groups;
|
|
||||||
if (config.num_groups != 0 &&
|
|
||||||
!config.executor_to_cpu_assignments.empty()) {
|
|
||||||
std::cout << "WARNING: num_groups and executor_to_cpu_assignments are both "
|
|
||||||
"set. This is not supported. Ignoring num_groups."
|
|
||||||
<< std::endl;
|
|
||||||
}
|
|
||||||
for (uint32_t i = 0; i < config.num_groups; i++) {
|
|
||||||
cb_groups.push_back(config.nodes[0]->create_callback_group(
|
|
||||||
rclcpp::CallbackGroupType::MutuallyExclusive));
|
|
||||||
}
|
|
||||||
|
|
||||||
// for each chain
|
|
||||||
for (uint32_t chain_id = 0; chain_id < config.callback_ids.size(); chain_id++) {
|
|
||||||
all_nodes.push_back(std::vector<std::shared_ptr<CaseStudyNode>>());
|
|
||||||
|
|
||||||
// for each node in the chain
|
|
||||||
bool first_node = true;
|
|
||||||
for (uint32_t node_chain_idx = 0;
|
|
||||||
node_chain_idx < config.callback_ids[chain_id].size(); node_chain_idx++) {
|
|
||||||
uint32_t const node_id = config.callback_ids[chain_id][node_chain_idx];
|
|
||||||
// has this node been created yet?
|
|
||||||
if (nodes.find(node_id) != nodes.end()) {
|
|
||||||
// if it has, then this node exists in another chain
|
|
||||||
// if it's a timer node, we should add it to the timer handle map
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
experiment_executor executor = getExecutorForNode(node_id);
|
|
||||||
std::shared_ptr<const void> handle;
|
|
||||||
priority_executor::ExecutableType type;
|
|
||||||
// is this the first node in the chain?
|
|
||||||
if (node_chain_idx == 0) {
|
|
||||||
// create a timer node
|
|
||||||
rclcpp::CallbackGroup::SharedPtr cb_group = nullptr;
|
|
||||||
if (jiang2022_cs2) {
|
|
||||||
std::cout << "jiang2022_cs2: placing all nodes in cb_group0"
|
|
||||||
<< std::endl;
|
|
||||||
cb_group = cb_group0;
|
|
||||||
}
|
|
||||||
if (config.num_groups != 0 && config.group_memberships[node_id] != 0) {
|
|
||||||
cb_group = cb_groups[config.group_memberships[node_id] - 1];
|
|
||||||
}
|
|
||||||
std::shared_ptr<PublisherNode> node;
|
|
||||||
if (config.callback_executor_assignments.empty()) {
|
|
||||||
node = std::make_shared<PublisherNode>(
|
|
||||||
"node_" + std::to_string(node_id), config.callback_runtimes[node_id],
|
|
||||||
config.chain_periods[chain_id], chain_id, config.nodes[0],
|
|
||||||
cb_group);
|
|
||||||
} else {
|
|
||||||
node = std::make_shared<PublisherNode>(
|
|
||||||
"node_" + std::to_string(node_id), config.callback_runtimes[node_id],
|
|
||||||
config.chain_periods[chain_id], chain_id,
|
|
||||||
config.nodes[config.callback_executor_assignments[node_id]],
|
|
||||||
cb_group);
|
|
||||||
}
|
|
||||||
handle = node->timer_->get_timer_handle();
|
|
||||||
nodes[node_id] = node;
|
|
||||||
type = priority_executor::ExecutableType::TIMER;
|
|
||||||
// this_chain_timer_handle = node->timer_;
|
|
||||||
std::cout << "chain timer handle set: " << chain_id << std::endl;
|
|
||||||
chain_timer_handles[chain_id] = node->timer_;
|
|
||||||
|
|
||||||
all_nodes[chain_id].push_back(node);
|
|
||||||
} else {
|
|
||||||
// create a subscriber node
|
|
||||||
int const subscription_id = config.callback_ids[chain_id][node_chain_idx - 1];
|
|
||||||
rclcpp::CallbackGroup::SharedPtr cb_group;
|
|
||||||
if (jiang2022_cb && ((chain_id == 1 && node_chain_idx == 1) ||
|
|
||||||
(chain_id == 2 && node_chain_idx == 1))) {
|
|
||||||
cb_group = cb_group0;
|
|
||||||
std::cout << "adding c: " << chain_id << " n: " << node_id
|
|
||||||
<< " to cb group 0" << std::endl;
|
|
||||||
} else {
|
|
||||||
cb_group = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.num_groups != 0 && config.group_memberships[node_id] != 0) {
|
|
||||||
cb_group = cb_groups[config.group_memberships[node_id] - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<WorkerNode> node;
|
|
||||||
if (ours2024_latency) {
|
|
||||||
uint32_t const first_node_id = config.callback_ids[chain_id][0];
|
|
||||||
node = std::make_shared<WorkerNode>(
|
|
||||||
"node_" + std::to_string(first_node_id),
|
|
||||||
"node_" + std::to_string(node_id), config.callback_runtimes[node_id],
|
|
||||||
config.chain_periods[chain_id], chain_id, config.nodes[0],
|
|
||||||
cb_group);
|
|
||||||
} else if (config.callback_executor_assignments.empty()) {
|
|
||||||
node = std::make_shared<WorkerNode>(
|
|
||||||
"node_" + std::to_string(subscription_id),
|
|
||||||
"node_" + std::to_string(node_id), config.callback_runtimes[node_id],
|
|
||||||
config.chain_periods[chain_id], chain_id, config.nodes[0],
|
|
||||||
cb_group);
|
|
||||||
} else {
|
|
||||||
node = std::make_shared<WorkerNode>(
|
|
||||||
"node_" + std::to_string(subscription_id),
|
|
||||||
"node_" + std::to_string(node_id), config.callback_runtimes[node_id],
|
|
||||||
config.chain_periods[chain_id], chain_id,
|
|
||||||
config.nodes[config.callback_executor_assignments[node_id]],
|
|
||||||
cb_group);
|
|
||||||
}
|
|
||||||
handle = node->sub_->get_subscription_handle();
|
|
||||||
nodes[node_id] = node;
|
|
||||||
type = priority_executor::ExecutableType::SUBSCRIPTION;
|
|
||||||
|
|
||||||
all_nodes[chain_id].push_back(node);
|
|
||||||
}
|
|
||||||
if (config.executor_type == "edf") {
|
|
||||||
std::string const cb_name = "node_" + std::to_string(node_id) + "_cb";
|
|
||||||
executor.strat->set_executable_deadline(
|
|
||||||
handle, config.chain_periods[chain_id], type, chain_id, cb_name);
|
|
||||||
// executor.executor->add_node(nodes[node_id]);
|
|
||||||
// make sure timer handle exists. if not, we did something wrong
|
|
||||||
if (chain_timer_handles.find(config.chain_timer_control[chain_id]) ==
|
|
||||||
chain_timer_handles.end()) {
|
|
||||||
std::cerr << "chain timer handle not found for chain " << chain_id
|
|
||||||
<< ": " << config.chain_timer_control[chain_id]
|
|
||||||
<< std::endl;
|
|
||||||
}
|
|
||||||
executor.strat->get_priority_settings(handle)->timer_handle =
|
|
||||||
chain_timer_handles[config.chain_timer_control[chain_id]];
|
|
||||||
executor.strat->get_priority_settings(handle)->priority =
|
|
||||||
config.callback_priorities[node_id]; // use priority as tiebreaker
|
|
||||||
if (first_node) {
|
|
||||||
executor.strat->set_first_in_chain(handle);
|
|
||||||
first_node = false;
|
|
||||||
}
|
|
||||||
// is this the last node in the chain?
|
|
||||||
if (node_chain_idx == config.callback_ids[chain_id].size() - 1) {
|
|
||||||
executor.strat->set_last_in_chain(handle);
|
|
||||||
}
|
|
||||||
} else if (config.executor_type == "picas") {
|
|
||||||
// executor.executor->add_node(nodes[node_id]);
|
|
||||||
executor.strat->set_executable_priority(
|
|
||||||
handle, config.callback_priorities[node_id], type,
|
|
||||||
priority_executor::ExecutableScheduleType::CHAIN_AWARE_PRIORITY, chain_id);
|
|
||||||
std::cout << "node " << node_id << " has priority "
|
|
||||||
<< config.callback_priorities[node_id] << std::endl;
|
|
||||||
} else if (config.executor_type == "default") {
|
|
||||||
// executor.default_executor->add_node(nodes[node_id]);
|
|
||||||
}
|
|
||||||
// TODO: other types
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Experiment::buildLogs(std::vector<node_time_logger> node_logs) {
|
|
||||||
std::stringstream output_file;
|
|
||||||
|
|
||||||
output_file << "[" << std::endl;
|
|
||||||
// for each node
|
|
||||||
for (auto const &node : nodes) {
|
|
||||||
// print the node's logs
|
|
||||||
node_time_logger const logger = node.second->logger;
|
|
||||||
for (auto const &log : *(logger.recorded_times)) {
|
|
||||||
output_file << "{\"entry\": " << log.first
|
|
||||||
<< ", \"time\": " << log.second << "}," << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.executor_type == "edf" || config.executor_type == "picas") {
|
|
||||||
for (auto const &executor : executors) {
|
|
||||||
node_time_logger const strat_log = executor.strat->logger_;
|
|
||||||
for (auto const &log : *(strat_log.recorded_times)) {
|
|
||||||
// std::cout << log.first << " " << log.second << std::endl;
|
|
||||||
output_file << "{\"entry\": " << log.first
|
|
||||||
<< ", \"time\": " << log.second << "}," << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// executor may be a subclass of RTISTimed, so we can get its logger
|
|
||||||
if (auto const *timed_executor = dynamic_cast<priority_executor::RTISTimed*>(executor.executor.get())) {
|
|
||||||
node_time_logger const executor_log = timed_executor->logger_;
|
|
||||||
for (auto const &log : *(executor_log.recorded_times)) {
|
|
||||||
output_file << "{\"entry\": " << log.first
|
|
||||||
<< ", \"time\": " << log.second << "}," << std::endl;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::cout << "executor is not RTISTimed" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// a default executor
|
|
||||||
for (auto const &executor : executors) {
|
|
||||||
// experimental: executor.default_executor's type is rclcpp::Executor, but
|
|
||||||
// here it's guaranteed to inherit from RTISTimed we can get a logger from
|
|
||||||
// it. for safety, we check the type first
|
|
||||||
if (auto const* timed_executor = dynamic_cast<priority_executor::RTISTimed*>(executor.default_executor.get())) {
|
|
||||||
node_time_logger const default_log = timed_executor->logger_;
|
|
||||||
for (auto const &log : *(default_log.recorded_times)) {
|
|
||||||
output_file << "{\"entry\": " << log.first
|
|
||||||
<< ", \"time\": " << log.second << "}," << std::endl;
|
|
||||||
}
|
|
||||||
std::cout << "recovered logs from default executor" << std::endl;
|
|
||||||
} else {
|
|
||||||
std::cout << "default executor is not RTISTimed" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add internal logs
|
|
||||||
for (auto const &log_entry : *(_logger.recorded_times)) {
|
|
||||||
output_file << "{\"entry\": " << log_entry.first
|
|
||||||
<< ", \"time\": " << log_entry.second << "}," << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add logs from argument
|
|
||||||
for (auto const &log : node_logs) {
|
|
||||||
for (auto const &log_entry : *(log.recorded_times)) {
|
|
||||||
output_file << "{\"entry\": " << log_entry.first
|
|
||||||
<< ", \"time\": " << log_entry.second << "}," << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove the last comma
|
|
||||||
output_file.seekp(-2, std::ios_base::end);
|
|
||||||
output_file << "]" << std::endl;
|
|
||||||
|
|
||||||
return output_file.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Experiment::setInitialDeadlines() {
|
|
||||||
timespec current_time;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC_RAW, ¤t_time);
|
|
||||||
uint64_t const millis =
|
|
||||||
(current_time.tv_sec * 1000UL) + (current_time.tv_nsec / 1000000);
|
|
||||||
// we need a logger to record the first deadline, which is generated here
|
|
||||||
// TODO: generate the first deadline in the executor (or node) itself
|
|
||||||
if (config.executor_type == "edf") {
|
|
||||||
for (uint32_t chain_index = 0; chain_index < config.chain_lengths.size();
|
|
||||||
chain_index++) {
|
|
||||||
size_t executor_idx;
|
|
||||||
if (config.callback_executor_assignments.empty()) {
|
|
||||||
executor_idx = 0;
|
|
||||||
} else {
|
|
||||||
executor_idx = config.callback_executor_assignments[chain_index];
|
|
||||||
}
|
|
||||||
std::shared_ptr<std::deque<uint64_t>> deadlines =
|
|
||||||
executors[executor_idx].strat->get_chain_deadlines(chain_index);
|
|
||||||
deadlines->push_back(millis + config.chain_periods[chain_index] * 2);
|
|
||||||
std::stringstream oss;
|
|
||||||
oss << "{\"operation\": \"next_deadline\", \"chain_id\": " << chain_index
|
|
||||||
<< ", \"deadline\": "
|
|
||||||
<< millis + config.chain_periods[chain_index] * 2 << "}";
|
|
||||||
// std::cout<<"initial deadline: "<<millis +
|
|
||||||
// config.chain_periods[chain_index] * 2<<std::endl; std::cout<<"current
|
|
||||||
// time: "<<millis<<std::endl; log_entry(executor.strat->logger_,
|
|
||||||
// oss.str());
|
|
||||||
log_entry(_logger, oss.str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Experiment::resetTimers() {
|
|
||||||
for (auto const &timer : chain_timer_handles) {
|
|
||||||
std::cout << "resetting timer " << timer.first << std::endl;
|
|
||||||
if (timer.second == nullptr) {
|
|
||||||
std::cout << "timer is null" << std::endl;
|
|
||||||
} else {
|
|
||||||
timer.second->reset();
|
|
||||||
/* int64_t old_period;
|
|
||||||
// set the period to 0 for force immediate execution
|
|
||||||
rcl_timer_exchange_period(timer.second->get_timer_handle().get(), 0,
|
|
||||||
&old_period);
|
|
||||||
// timer.second->get_timer_handle() */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// sleep 1 seconds to let every timer reset
|
|
||||||
// std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Experiment::writeLogsToFile(std::string const &file_name,
|
|
||||||
std::string const &experiment_name) {
|
|
||||||
std::filesystem::path results_dir = "results";
|
|
||||||
std::filesystem::path exp_dir = results_dir / experiment_name;
|
|
||||||
|
|
||||||
// Create directories if they don't exist
|
|
||||||
try {
|
|
||||||
std::filesystem::create_directories(exp_dir);
|
|
||||||
} catch (std::filesystem::filesystem_error const &e) {
|
|
||||||
std::cout << "Could not create results directory: " << e.what() << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string output_filename = (file_name + "_" + this->config.executor_type + ".json");
|
|
||||||
std::filesystem::path output_path = exp_dir / output_filename;
|
|
||||||
|
|
||||||
std::ofstream output_file(output_path);
|
|
||||||
output_file << buildLogs();
|
|
||||||
output_file.close();
|
|
||||||
|
|
||||||
std::cout << "results written to " << output_path.string() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
int do_other_task(double const work_time, uint const period,
|
|
||||||
std::atomic<bool>& should_do_task) {
|
|
||||||
if (work_time == 0) {
|
|
||||||
std::cout << "other task: work time is 0, exiting" << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// pin to CPU 0 with highest priority
|
|
||||||
cpu_set_t cpuset;
|
|
||||||
CPU_ZERO(&cpuset);
|
|
||||||
CPU_SET(0, &cpuset);
|
|
||||||
int const result = sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
|
|
||||||
if (result != 0) {
|
|
||||||
std::cout << "other task: could not set affinity: " << result << ": "
|
|
||||||
<< strerror(errno) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sched_param param;
|
|
||||||
param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 1;
|
|
||||||
sched_setscheduler(0, SCHED_FIFO, ¶m);
|
|
||||||
if (result != 0) {
|
|
||||||
std::cout << "other task: could not set scheduler: " << result << ": "
|
|
||||||
<< strerror(errno) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
prctl(PR_SET_NAME, "other_task", 0, 0, 0);
|
|
||||||
|
|
||||||
// do every period milliseconds
|
|
||||||
do {
|
|
||||||
timespec current_time;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC_RAW, ¤t_time);
|
|
||||||
uint64_t const millis_start = (current_time.tv_sec * (uint64_t)1000) +
|
|
||||||
(current_time.tv_nsec / 1000000);
|
|
||||||
|
|
||||||
// do work
|
|
||||||
// nth_prime_silly(work_time);
|
|
||||||
dummy_load(work_time);
|
|
||||||
|
|
||||||
// sleep until next period
|
|
||||||
clock_gettime(CLOCK_MONOTONIC_RAW, ¤t_time);
|
|
||||||
uint64_t const millis_end = (current_time.tv_sec * (uint64_t)1000) +
|
|
||||||
(current_time.tv_nsec / 1000000);
|
|
||||||
uint64_t const sleep_time = period - (millis_end - millis_start);
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
|
||||||
} while (should_do_task);
|
|
||||||
std::cout << "other task done" << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
#include <chrono>
|
|
||||||
#include <thread>
|
|
||||||
#include <atomic>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "casestudy_tools/primes_workload.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
void DummyLoadCalibration::setCalibration(int const calib) {
|
|
||||||
calibration.store(calib, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
int DummyLoadCalibration::getCalibration() {
|
|
||||||
return calibration.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void calibrate_dummy_load()
|
|
||||||
{
|
|
||||||
int calibration_factor = 1;
|
|
||||||
for (int iteration = 0; iteration < MAX_ITERATIONS; ++iteration)
|
|
||||||
{
|
|
||||||
// Measure the time taken for the dummy load
|
|
||||||
auto const start_time = std::chrono::high_resolution_clock::now();
|
|
||||||
dummy_load(TARGET_DURATION_MS); // intended workload
|
|
||||||
auto const end_time = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
// Calculate duration in microseconds
|
|
||||||
auto const duration_us =
|
|
||||||
std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
|
|
||||||
|
|
||||||
std::cout << "[Calibration] Iteration: " << iteration
|
|
||||||
<< ", Calibration Factor: " << calibration_factor
|
|
||||||
<< ", Measured Duration: " << duration_us << " µs" << std::endl;
|
|
||||||
|
|
||||||
// Check if within acceptable margin
|
|
||||||
if (std::abs(duration_us - TARGET_DURATION_MS * 1000) < ACCEPTABLE_MARGIN_US)
|
|
||||||
{
|
|
||||||
std::cout << "[Calibration] Converged successfully." << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust calibration factor proportionally
|
|
||||||
calibration_factor = (TARGET_DURATION_MS * 1000 * calibration_factor) / duration_us;
|
|
||||||
|
|
||||||
if (calibration_factor <= 0)
|
|
||||||
{
|
|
||||||
calibration_factor = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
DummyLoadCalibration::setCalibration(calibration_factor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dummy_load(int const load_ms)
|
|
||||||
{
|
|
||||||
int const calib_factor = DummyLoadCalibration::getCalibration();
|
|
||||||
|
|
||||||
// calibration factor corresponds to 100ms load, scale it
|
|
||||||
int const scaled_factor = calib_factor * load_ms / 100;
|
|
||||||
|
|
||||||
volatile int dummy_var = 0;
|
|
||||||
constexpr int DUMMY_LOAD_ITER = 100'000;
|
|
||||||
|
|
||||||
for (int j = 0; j < scaled_factor; ++j)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < DUMMY_LOAD_ITER; ++i)
|
|
||||||
{
|
|
||||||
dummy_var += i; // clearer dummy operation (optimizations prevented by volatile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,180 +0,0 @@
|
||||||
#include "casestudy_tools/test_nodes.hpp"
|
|
||||||
#include "casestudy_tools/primes_workload.hpp"
|
|
||||||
#include "rclcpp/rclcpp.hpp"
|
|
||||||
#include "simple_timer/rt-sched.hpp"
|
|
||||||
#include <cstddef>
|
|
||||||
#include <ctime>
|
|
||||||
#include <rclcpp/logger.hpp>
|
|
||||||
|
|
||||||
std::string CaseStudyNode::get_name() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define BUFFER_LENGTH 0
|
|
||||||
|
|
||||||
rclcpp::QoS get_qos() {
|
|
||||||
if (BUFFER_LENGTH == 0) {
|
|
||||||
return rclcpp::QoS(rclcpp::KeepAll());
|
|
||||||
}
|
|
||||||
return rclcpp::QoS(rclcpp::KeepLast(BUFFER_LENGTH));
|
|
||||||
}
|
|
||||||
|
|
||||||
void timespec_diff(timespec const* start, timespec const* end, timespec* result) {
|
|
||||||
if ((end->tv_nsec - start->tv_nsec) < 0) {
|
|
||||||
result->tv_sec = end->tv_sec - start->tv_sec - 1;
|
|
||||||
result->tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec;
|
|
||||||
} else {
|
|
||||||
result->tv_sec = end->tv_sec - start->tv_sec;
|
|
||||||
result->tv_nsec = end->tv_nsec - start->tv_nsec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PublisherNode::PublisherNode(
|
|
||||||
std::string const &publish_topic,
|
|
||||||
double const runtime,
|
|
||||||
int const period,
|
|
||||||
int const chain,
|
|
||||||
rclcpp::Node::SharedPtr const &node,
|
|
||||||
rclcpp::CallbackGroup::SharedPtr const &callback_group)
|
|
||||||
: CaseStudyNode(publish_topic, node)
|
|
||||||
, runtime_(runtime)
|
|
||||||
, period_(period)
|
|
||||||
, chain_(chain) {
|
|
||||||
|
|
||||||
logger = create_logger();
|
|
||||||
pub_ = node->create_publisher<std_msgs::msg::Int32>(publish_topic, get_qos());
|
|
||||||
|
|
||||||
auto const timer_callback = [this]() -> void {
|
|
||||||
std::ostringstream ss;
|
|
||||||
ss << "{\"operation\": \"start_work\", \"chain\": " << chain_
|
|
||||||
<< ", \"node\": \"" << get_name() << "\", \"count\": " << count_max_;
|
|
||||||
|
|
||||||
std::chrono::nanoseconds const time_until_trigger = timer_->time_until_trigger();
|
|
||||||
std::chrono::microseconds const time_until_trigger_us =
|
|
||||||
std::chrono::duration_cast<std::chrono::microseconds>(time_until_trigger);
|
|
||||||
|
|
||||||
ss << ", \"next_release_us\": " << time_until_trigger_us.count() << "}";
|
|
||||||
log_entry(logger, ss.str());
|
|
||||||
|
|
||||||
double used_runtime = runtime_;
|
|
||||||
if (use_random_runtime) {
|
|
||||||
if (rand() % 100 < runtime_over_chance * 100) {
|
|
||||||
used_runtime *= over_runtime_scale;
|
|
||||||
} else {
|
|
||||||
used_runtime *= normal_runtime_scale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RCLCPP_INFO(rclcpp::get_logger(get_name()), "running %s with runtime %f",
|
|
||||||
// get_name().c_str(), used_runtime);
|
|
||||||
|
|
||||||
struct timespec start_time;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
|
||||||
|
|
||||||
dummy_load(used_runtime);
|
|
||||||
std_msgs::msg::Int32 msg;
|
|
||||||
msg.data = count_max_;
|
|
||||||
// RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", msg.data.c_str());
|
|
||||||
pub_->publish(msg);
|
|
||||||
// TEST: can we take more than one msg from DDS?
|
|
||||||
// pub_->publish(msg);
|
|
||||||
|
|
||||||
ss.str("");
|
|
||||||
ss << "{\"operation\": \"end_work\", \"chain\": " << chain_
|
|
||||||
<< ", \"node\": \"" << get_name() << "\", \"count\": " << count_max_
|
|
||||||
<< ", \"target_runtime\": " << runtime_ << "}";
|
|
||||||
log_entry(logger, ss.str());
|
|
||||||
|
|
||||||
count_max_--;
|
|
||||||
if (count_max_ == 0) {
|
|
||||||
rclcpp::shutdown();
|
|
||||||
}
|
|
||||||
/* struct timespec end_time;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
|
||||||
struct timespec diff;
|
|
||||||
timespec_diff(&start_time, &end_time, &diff);
|
|
||||||
size_t diff_ns = diff.tv_sec * 1000000000 + diff.tv_nsec;
|
|
||||||
size_t diff_ms = diff_ns / 1000000;
|
|
||||||
RCLCPP_INFO(rclcpp::get_logger(get_name()), "done running %s, took %lu ms",
|
|
||||||
get_name().c_str(), diff_ms); */
|
|
||||||
};
|
|
||||||
|
|
||||||
if (callback_group != nullptr) {
|
|
||||||
callback_group_ = callback_group;
|
|
||||||
} else {
|
|
||||||
callback_group_ = node->create_callback_group(
|
|
||||||
rclcpp::CallbackGroupType::Reentrant);
|
|
||||||
}
|
|
||||||
|
|
||||||
timer_ = node->create_wall_timer(
|
|
||||||
std::chrono::milliseconds(period),
|
|
||||||
timer_callback,
|
|
||||||
callback_group_);
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkerNode::WorkerNode(
|
|
||||||
std::string const &subscribe_topic,
|
|
||||||
std::string const &publish_topic,
|
|
||||||
double const runtime,
|
|
||||||
int const period,
|
|
||||||
int const chain,
|
|
||||||
rclcpp::Node::SharedPtr const &node,
|
|
||||||
rclcpp::CallbackGroup::SharedPtr const &callback_group)
|
|
||||||
: CaseStudyNode(publish_topic, node)
|
|
||||||
, runtime_(runtime)
|
|
||||||
, period_(period)
|
|
||||||
, chain_(chain) {
|
|
||||||
|
|
||||||
logger = create_logger();
|
|
||||||
|
|
||||||
auto const callback = [this](std_msgs::msg::Int32::SharedPtr const msg) -> void {
|
|
||||||
// prevent unused variable warning
|
|
||||||
(void)msg;
|
|
||||||
|
|
||||||
std::ostringstream ss;
|
|
||||||
ss << "{\"operation\": \"start_work\", \"chain\": " << chain_
|
|
||||||
<< ", \"node\": \"" << get_name() << "\", \"count\": " << msg->data
|
|
||||||
<< "}";
|
|
||||||
log_entry(logger, ss.str());
|
|
||||||
// RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
|
|
||||||
// nth_prime_silly(runtime_);
|
|
||||||
|
|
||||||
// TODO: investigate jitter generation
|
|
||||||
double used_runtime = runtime_;
|
|
||||||
if (use_random_runtime) {
|
|
||||||
if (rand() % 100 < runtime_over_chance * 100) {
|
|
||||||
used_runtime *= over_runtime_scale;
|
|
||||||
} else {
|
|
||||||
used_runtime *= normal_runtime_scale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dummy_load(used_runtime);
|
|
||||||
// RCLCPP_INFO(this->get_logger(), "Result: %d", result);
|
|
||||||
auto new_msg = std_msgs::msg::Int32();
|
|
||||||
new_msg.data = msg->data;
|
|
||||||
pub_->publish(new_msg);
|
|
||||||
|
|
||||||
ss.str("");
|
|
||||||
ss << "{\"operation\": \"end_work\", \"chain\": " << chain_
|
|
||||||
<< ", \"node\": \"" << get_name() << "\", \"count\": " << msg->data
|
|
||||||
<< "}";
|
|
||||||
log_entry(logger, ss.str());
|
|
||||||
};
|
|
||||||
|
|
||||||
rclcpp::SubscriptionOptions sub_options;
|
|
||||||
if (callback_group) {
|
|
||||||
callback_group_ = callback_group;
|
|
||||||
} else {
|
|
||||||
callback_group_ = node->create_callback_group(
|
|
||||||
rclcpp::CallbackGroupType::Reentrant);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub_options.callback_group = callback_group_;
|
|
||||||
sub_ = node->create_subscription<std_msgs::msg::Int32>(
|
|
||||||
subscribe_topic,
|
|
||||||
get_qos(),
|
|
||||||
callback,
|
|
||||||
sub_options);
|
|
||||||
pub_ = node->create_publisher<std_msgs::msg::Int32>(publish_topic, get_qos());
|
|
||||||
}
|
|
51
src/full_topology/src/Untitled-1
Normal file
51
src/full_topology/src/Untitled-1
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// A generic single-input processing node with a fixed delay +/- jitter
|
||||||
|
#define DEFINE_MULTI_PROCESSING_NODE_CLASS(NAME) \
|
||||||
|
class NAME : public rclcpp::Node\
|
||||||
|
{\
|
||||||
|
public:\
|
||||||
|
NAME(std::string const &name,\
|
||||||
|
std::vector<std::string> const &input_topics,\
|
||||||
|
std::string const &output_topic,\
|
||||||
|
int delay_ms,\
|
||||||
|
double jitter_ms = 0.0) \
|
||||||
|
: Node(name)\
|
||||||
|
{\
|
||||||
|
for (auto &topic : input_topics)\
|
||||||
|
{\
|
||||||
|
auto sub = create_subscription<std_msgs::msg::String>(\
|
||||||
|
topic, 10,\
|
||||||
|
[this, name, delay_ms, jitter_ms](std_msgs::msg::String::SharedPtr msg) \
|
||||||
|
{\
|
||||||
|
/* simple fusion: log each arrival */\
|
||||||
|
if (DEBUG)\
|
||||||
|
{\
|
||||||
|
RCLCPP_INFO(get_logger(), "%s fusing input: %s", name.c_str(), msg->data.c_str());\
|
||||||
|
}\
|
||||||
|
#ifndef USE_TIMER_IN_FUSION_NODES\
|
||||||
|
// Simulate processing delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
|
||||||
|
auto out = std_msgs::msg::String();
|
||||||
|
out.data = msg->data + " -> " + name + std::string(" fused");
|
||||||
|
if (DEBUG) {
|
||||||
|
RCLCPP_INFO(get_logger(), "%s publishes fused message", name.c_str());
|
||||||
|
}
|
||||||
|
publisher_->publish(out);
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
subscriptions_.emplace_back(sub);
|
||||||
|
}
|
||||||
|
publisher_ = create_publisher<std_msgs::msg::String>(output_topic, 10);
|
||||||
|
|
||||||
|
#ifdef USE_TIMER_IN_FUSION_NODES
|
||||||
|
// Also publish fused result periodically
|
||||||
|
timer_ = create_wall_timer(50ms, [this, name, delay_ms]()
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
|
||||||
|
auto out = std_msgs::msg::String();
|
||||||
|
out.data = name + std::string(" fused");
|
||||||
|
if (DEBUG) {
|
||||||
|
RCLCPP_INFO(get_logger(), "%s publishes fused message", name.c_str());
|
||||||
|
}
|
||||||
|
publisher_->publish(out); });
|
||||||
|
#endif
|
||||||
|
}
|
|
@ -2,239 +2,182 @@
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <random>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
// the order here matters
|
// the order here matters
|
||||||
#include "tracetools/utils.hpp"
|
#include "tracetools/utils.hpp"
|
||||||
|
|
||||||
#include <rclcpp/rclcpp.hpp>
|
#include <rclcpp/rclcpp.hpp>
|
||||||
#include "std_msgs/msg/string.hpp"
|
#include "std_msgs/msg/string.hpp"
|
||||||
|
|
||||||
|
/** Whether to use timers in fusion nodes */
|
||||||
|
//#define USE_TIMER_IN_FUSION_NODES
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
constexpr bool DEBUG = false;
|
constexpr bool DEBUG = false;
|
||||||
|
|
||||||
// A generic sensor node that publishes at a given rate
|
// A generic sensor node that publishes at a given rate + jitter
|
||||||
class SensorNode : public rclcpp::Node
|
#define DEFINE_SENSOR_NODE_CLASS(NAME) \
|
||||||
{
|
class NAME : public rclcpp::Node \
|
||||||
public:
|
{ \
|
||||||
SensorNode(std::string const &name,
|
public: \
|
||||||
std::string const &topic,
|
NAME(std::string const &name, \
|
||||||
int rate_hz)
|
std::string const &topic, \
|
||||||
: Node(name)
|
int rate_hz, \
|
||||||
{
|
double jitter_ms_ = 0.0) \
|
||||||
publisher_ = create_publisher<std_msgs::msg::String>(topic, 10);
|
: Node(name) \
|
||||||
auto period = std::chrono::milliseconds(1000 / rate_hz);
|
{ \
|
||||||
timer_ = create_wall_timer(period, [this, name]()
|
publisher_ = create_publisher<std_msgs::msg::String>(topic, 10); \
|
||||||
{
|
auto period = std::chrono::milliseconds(1000 / rate_hz); \
|
||||||
auto msg = std_msgs::msg::String();
|
timer_ = create_wall_timer(period, [this, name, jitter_ms_]() \
|
||||||
msg.data = name + std::string(" publishes");
|
{ \
|
||||||
if (DEBUG) {
|
double jitter = 0.0; \
|
||||||
RCLCPP_INFO(get_logger(), "%s", msg.data.c_str());
|
if (jitter_ms_ > 0.0) { \
|
||||||
}
|
static thread_local std::mt19937 gen(std::random_device{}()); \
|
||||||
publisher_->publish(msg); });
|
std::normal_distribution<> d(0.0, jitter_ms_); \
|
||||||
}
|
jitter = d(gen); \
|
||||||
|
} \
|
||||||
private:
|
/* Ensure total delay is non-negative */ \
|
||||||
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
|
int total_delay = std::max(0, static_cast<int>(std::round(jitter))); \
|
||||||
rclcpp::TimerBase::SharedPtr timer_;
|
std::this_thread::sleep_for(std::chrono::milliseconds(total_delay)); \
|
||||||
|
auto msg = std_msgs::msg::String(); \
|
||||||
|
msg.data = name + std::string(" publishes"); \
|
||||||
|
if (DEBUG) { \
|
||||||
|
RCLCPP_INFO(get_logger(), "%s", msg.data.c_str()); \
|
||||||
|
} \
|
||||||
|
publisher_->publish(msg); }); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
private: \
|
||||||
|
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; \
|
||||||
|
rclcpp::TimerBase::SharedPtr timer_; \
|
||||||
};
|
};
|
||||||
|
|
||||||
// A generic single-input processing node with a fixed delay
|
// A generic single-input processing node with a fixed delay +/- jitter
|
||||||
class ProcessingNode : public rclcpp::Node
|
#define DEFINE_PROCESSING_NODE_CLASS(NAME) \
|
||||||
{
|
class NAME : public rclcpp::Node \
|
||||||
public:
|
{ \
|
||||||
ProcessingNode(std::string const &name,
|
public: \
|
||||||
std::string const &input_topic,
|
NAME(const std::string & name, \
|
||||||
std::string const &output_topic,
|
const std::string & input_topic, \
|
||||||
int delay_ms)
|
const std::string & output_topic, \
|
||||||
: Node(name)
|
int delay_ms, \
|
||||||
{
|
double jitter_ms = 0.0) \
|
||||||
subscription_ = create_subscription<std_msgs::msg::String>(
|
: Node(name), delay_ms_(delay_ms), jitter_ms_(jitter_ms) \
|
||||||
input_topic, 10,
|
{ \
|
||||||
[this, name, output_topic, delay_ms](std_msgs::msg::String::SharedPtr msg)
|
subscription_ = create_subscription<std_msgs::msg::String>( \
|
||||||
{
|
input_topic, 10, \
|
||||||
if (DEBUG) {
|
[this, name](std_msgs::msg::String::SharedPtr msg) \
|
||||||
RCLCPP_INFO(get_logger(), "%s received: %s", name.c_str(), msg->data.c_str());
|
{ \
|
||||||
}
|
if (DEBUG) { \
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
|
RCLCPP_INFO(get_logger(), "%s received: %s", name.c_str(), msg->data.c_str()); \
|
||||||
auto out = std_msgs::msg::String();
|
} \
|
||||||
out.data = msg->data + " -> " + name;
|
/* Simulate processing delay */ \
|
||||||
publisher_->publish(out);
|
double jitter = 0.0; \
|
||||||
});
|
if (jitter_ms_ > 0.0) { \
|
||||||
publisher_ = create_publisher<std_msgs::msg::String>(output_topic, 10);
|
static thread_local std::mt19937 gen(std::random_device{}());\
|
||||||
}
|
std::normal_distribution<> d(0.0, jitter_ms_); \
|
||||||
|
jitter = d(gen); \
|
||||||
protected:
|
} \
|
||||||
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
|
std::this_thread::sleep_for(std::chrono::milliseconds((int)(delay_ms_ + jitter))); \
|
||||||
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
|
auto out = std_msgs::msg::String(); \
|
||||||
|
out.data = msg->data + " -> " + name; \
|
||||||
|
publisher_->publish(out); \
|
||||||
|
}); \
|
||||||
|
publisher_ = create_publisher<std_msgs::msg::String>(output_topic, 10); \
|
||||||
|
} \
|
||||||
|
protected: \
|
||||||
|
int delay_ms_; \
|
||||||
|
double jitter_ms_; \
|
||||||
|
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_; \
|
||||||
|
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; \
|
||||||
};
|
};
|
||||||
|
|
||||||
// A processing node that fuses multiple inputs
|
// A generic single-input processing node with a fixed delay +/- jitter
|
||||||
class MultiInputProcessingNode : public rclcpp::Node
|
#define DEFINE_MULTI_PROCESSING_NODE_CLASS(NAME) \
|
||||||
{
|
class NAME : public rclcpp::Node \
|
||||||
public:
|
{ \
|
||||||
MultiInputProcessingNode(std::string const &name,
|
public: \
|
||||||
std::vector<std::string> const &input_topics,
|
NAME(std::string const &name, \
|
||||||
std::string const &output_topic,
|
std::vector<std::string> const &input_topics, \
|
||||||
int delay_ms)
|
std::string const &output_topic, \
|
||||||
: Node(name)
|
int delay_ms, \
|
||||||
{
|
double jitter_ms = 0.0) \
|
||||||
for (auto &topic : input_topics)
|
: Node(name), delay_ms_(delay_ms), jitter_ms_(jitter_ms) \
|
||||||
{
|
{ \
|
||||||
auto sub = create_subscription<std_msgs::msg::String>(
|
for (auto &topic : input_topics) \
|
||||||
topic, 10,
|
{ \
|
||||||
[this, name, delay_ms](std_msgs::msg::String::SharedPtr msg)
|
auto sub = create_subscription<std_msgs::msg::String>( \
|
||||||
{
|
topic, 10, \
|
||||||
// simple fusion: log each arrival
|
[this, name](std_msgs::msg::String::SharedPtr msg) \
|
||||||
if (DEBUG)
|
{ \
|
||||||
{
|
/* simple fusion: log each arrival */ \
|
||||||
RCLCPP_INFO(get_logger(), "%s fusing input: %s", name.c_str(), msg->data.c_str());
|
if (DEBUG) \
|
||||||
}
|
{ \
|
||||||
});
|
RCLCPP_INFO(get_logger(), "%s fusing input: %s", name.c_str(), msg->data.c_str()); \
|
||||||
subscriptions_.push_back(sub);
|
} \
|
||||||
}
|
/* Simulate processing delay */ \
|
||||||
publisher_ = create_publisher<std_msgs::msg::String>(output_topic, 10);
|
double jitter = 0.0; \
|
||||||
// Also publish fused result periodically
|
if (jitter_ms_ > 0.0) { \
|
||||||
timer_ = create_wall_timer(50ms, [this, name, delay_ms]()
|
static thread_local std::mt19937 gen(std::random_device{}()); \
|
||||||
{
|
std::normal_distribution<> d(0.0, jitter_ms_); \
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
|
jitter = d(gen); \
|
||||||
auto out = std_msgs::msg::String();
|
} \
|
||||||
out.data = name + std::string(" fused");
|
std::this_thread::sleep_for(std::chrono::milliseconds((int)(delay_ms_ + jitter))); \
|
||||||
if (DEBUG) {
|
auto out = std_msgs::msg::String(); \
|
||||||
RCLCPP_INFO(get_logger(), "%s publishes fused message", name.c_str());
|
out.data = msg->data + " -> " + name; \
|
||||||
}
|
if (DEBUG) { \
|
||||||
publisher_->publish(out); });
|
RCLCPP_INFO(get_logger(), "%s publishes fused message", name.c_str()); \
|
||||||
}
|
} \
|
||||||
|
publisher_->publish(out); \
|
||||||
protected:
|
}); \
|
||||||
std::vector<rclcpp::Subscription<std_msgs::msg::String>::SharedPtr> subscriptions_;
|
subscriptions_.emplace_back(sub); \
|
||||||
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
|
} \
|
||||||
rclcpp::TimerBase::SharedPtr timer_;
|
publisher_ = create_publisher<std_msgs::msg::String>(output_topic, 10); \
|
||||||
|
\
|
||||||
|
} \
|
||||||
|
protected: \
|
||||||
|
std::vector<rclcpp::Subscription<std_msgs::msg::String>::SharedPtr> subscriptions_; \
|
||||||
|
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; \
|
||||||
|
int delay_ms_; \
|
||||||
|
double jitter_ms_; \
|
||||||
};
|
};
|
||||||
|
|
||||||
class CameraNode : public SensorNode
|
DEFINE_SENSOR_NODE_CLASS(CameraNode)
|
||||||
{
|
DEFINE_PROCESSING_NODE_CLASS(DebayerNode)
|
||||||
public:
|
DEFINE_PROCESSING_NODE_CLASS(RadiometricNode)
|
||||||
CameraNode(std::string const &name, std::string const &topic, int rate_hz) : SensorNode(name, topic, rate_hz) {}
|
DEFINE_PROCESSING_NODE_CLASS(GeometricNode)
|
||||||
};
|
DEFINE_PROCESSING_NODE_CLASS(MappingNode)
|
||||||
|
|
||||||
class DebayerNode : public ProcessingNode
|
DEFINE_PROCESSING_NODE_CLASS(SmokeClassifierNode)
|
||||||
{
|
|
||||||
public:
|
|
||||||
DebayerNode(std::string const &name, std::string const &input_topic, std::string const &output_topic, int delay_ms) : ProcessingNode(name, input_topic, output_topic, delay_ms) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class RadiometricNode : public ProcessingNode
|
DEFINE_SENSOR_NODE_CLASS(GPSNode)
|
||||||
{
|
DEFINE_SENSOR_NODE_CLASS(IMUNode)
|
||||||
public:
|
DEFINE_SENSOR_NODE_CLASS(BaroNode)
|
||||||
RadiometricNode(std::string const &name, std::string const &input_topic, std::string const &output_topic, int delay_ms) : ProcessingNode(name, input_topic, output_topic, delay_ms) {
|
|
||||||
// Overwrite the subscription callback
|
|
||||||
subscription_ = create_subscription<std_msgs::msg::String>(
|
|
||||||
input_topic, 10,
|
|
||||||
[this, name, output_topic, delay_ms](std_msgs::msg::String::SharedPtr msg)
|
|
||||||
{
|
|
||||||
// Custom logic for RadiometricNode
|
|
||||||
if (DEBUG) {
|
|
||||||
RCLCPP_INFO(get_logger(), "%s received: %s", name.c_str(), msg->data.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO
|
DEFINE_PROCESSING_NODE_CLASS(FusionNodeS)
|
||||||
// random: 1 in 5 it will be 10s instead of delay_ms
|
DEFINE_MULTI_PROCESSING_NODE_CLASS(FusionNode)
|
||||||
if (rand() % 5 == 0) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
|
|
||||||
} else {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Custom transformation of data
|
DEFINE_SENSOR_NODE_CLASS(LidarNode)
|
||||||
auto out = std_msgs::msg::String();
|
|
||||||
out.data = msg->data + " -> Radiometric processing complete";
|
|
||||||
|
|
||||||
// Publish the processed message
|
|
||||||
publisher_->publish(out);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class GeometricNode : public ProcessingNode
|
DEFINE_SENSOR_NODE_CLASS(CommandNode)
|
||||||
{
|
|
||||||
public:
|
|
||||||
GeometricNode(std::string const &name, std::string const &input_topic, std::string const &output_topic, int delay_ms) : ProcessingNode(name, input_topic, output_topic, delay_ms) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class MappingNode : public ProcessingNode
|
DEFINE_PROCESSING_NODE_CLASS(FlightManagementNodeS)
|
||||||
{
|
|
||||||
public:
|
|
||||||
MappingNode(std::string const &name, std::string const &input_topic, std::string const &output_topic, int delay_ms) : ProcessingNode(name, input_topic, output_topic, delay_ms) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class SmokeClassifierNode : public ProcessingNode
|
DEFINE_MULTI_PROCESSING_NODE_CLASS(FlightManagementNode)
|
||||||
{
|
|
||||||
public:
|
|
||||||
SmokeClassifierNode(std::string const &name, std::string const &input_topic, std::string const &output_topic, int delay_ms) : ProcessingNode(name, input_topic, output_topic, delay_ms) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class GPSNode : public SensorNode
|
DEFINE_PROCESSING_NODE_CLASS(ControlNode)
|
||||||
{
|
|
||||||
public:
|
|
||||||
GPSNode(std::string const &name, std::string const &topic, int rate_hz) : SensorNode(name, topic, rate_hz) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class IMUNode : public SensorNode
|
DEFINE_PROCESSING_NODE_CLASS(TelemetryNodeS)
|
||||||
{
|
DEFINE_MULTI_PROCESSING_NODE_CLASS(TelemetryNode)
|
||||||
public:
|
|
||||||
IMUNode(std::string const &name, std::string const &topic, int rate_hz) : SensorNode(name, topic, rate_hz) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class BaroNode : public SensorNode
|
DEFINE_PROCESSING_NODE_CLASS(RadioNode)
|
||||||
{
|
|
||||||
public:
|
|
||||||
BaroNode(std::string const &name, std::string const &topic, int rate_hz) : SensorNode(name, topic, rate_hz) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class FusionNode : public MultiInputProcessingNode
|
#define PROCENTUAL_JITTER 0.1
|
||||||
{
|
#define DELAY_JITTER_PAIR(DELAY) DELAY, PROCENTUAL_JITTER * DELAY
|
||||||
public:
|
|
||||||
FusionNode(std::string const &name, std::vector<std::string> const &input_topics, std::string const &output_topic, int delay_ms) : MultiInputProcessingNode(name, input_topics, output_topic, delay_ms) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class LidarNode : public SensorNode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LidarNode(std::string const &name, std::string const &topic, int rate_hz) : SensorNode(name, topic, rate_hz) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class CommandNode : public SensorNode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CommandNode(std::string const &name, std::string const &topic, int rate_hz) : SensorNode(name, topic, rate_hz) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class FlightManagementNode : public MultiInputProcessingNode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
FlightManagementNode(std::string const &name, std::vector<std::string> const &input_topics, std::string const &output_topic, int delay_ms) : MultiInputProcessingNode(name, input_topics, output_topic, delay_ms) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ControlNode : public ProcessingNode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ControlNode(std::string const &name, std::string const &input_topic, std::string const &output_topic, int delay_ms) : ProcessingNode(name, input_topic, output_topic, delay_ms) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class TelemetryNode : public MultiInputProcessingNode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TelemetryNode(std::string const &name, std::vector<std::string> const &input_topics, std::string const &output_topic, int delay_ms) : MultiInputProcessingNode(name, input_topics, output_topic, delay_ms) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class RadioNode : public ProcessingNode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RadioNode(std::string const &name, std::string const &input_topic, std::string const &output_topic, int delay_ms) : ProcessingNode(name, input_topic, output_topic, delay_ms) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
@ -242,32 +185,32 @@ int main(int argc, char **argv)
|
||||||
rclcpp::executors::MultiThreadedExecutor executor;
|
rclcpp::executors::MultiThreadedExecutor executor;
|
||||||
|
|
||||||
// Chain 1: Image2Ground Mapping
|
// Chain 1: Image2Ground Mapping
|
||||||
auto camera = std::make_shared<CameraNode>("camera_node", "/camera/raw", 30);
|
auto camera = std::make_shared<CameraNode>("input_camera_node", "/input/camera/raw", DELAY_JITTER_PAIR(5)); // Sensor read, low latency
|
||||||
auto debayer = std::make_shared<DebayerNode>("debayer_node", "/camera/raw", "/camera/debayered", 15);
|
auto debayer = std::make_shared<DebayerNode>("debayer_node", "/input/camera/raw", "/camera/debayered", DELAY_JITTER_PAIR(18)); // Moderate, image conversion
|
||||||
auto radiometric = std::make_shared<RadiometricNode>("radiometric_node", "/camera/debayered", "/camera/radiometric", 20);
|
auto radiometric = std::make_shared<RadiometricNode>("radiometric_node", "/camera/debayered", "/camera/radiometric", DELAY_JITTER_PAIR(22)); // Heavier image math
|
||||||
auto geometric = std::make_shared<GeometricNode>("geometric_node", "/camera/radiometric", "/camera/geometric", 25);
|
auto geometric = std::make_shared<GeometricNode>("geometric_node", "/camera/radiometric", "/camera/geometric", DELAY_JITTER_PAIR(12)); // Perspective transform, medium
|
||||||
auto mapping = std::make_shared<MappingNode>("mapping_node", "/camera/geometric", "/camera/mapped", 30);
|
auto mapping = std::make_shared<MappingNode>("output_mapping_node", "/camera/geometric", "/output/camera/mapped", DELAY_JITTER_PAIR(16)); // Warping/stitching, moderate
|
||||||
|
|
||||||
// Chain 2: Semantic Scheduler Adaptation
|
// Chain 2: Semantic Scheduler Adaptation
|
||||||
auto smoke = std::make_shared<SmokeClassifierNode>("smoke_classifier_node", "/camera/radiometric", "/classifier/classification", 50);
|
auto smoke = std::make_shared<SmokeClassifierNode>("output_smoke_classifier_node", "/camera/radiometric", "/output/classifier/classification", DELAY_JITTER_PAIR(35)); // CNN/ML classifier, can be high
|
||||||
|
|
||||||
// Chain 3: Flight Control
|
// Chain 3: Flight Control
|
||||||
auto gps = std::make_shared<GPSNode>("gps_node", "/gps/fix", 10);
|
auto gps = std::make_shared<GPSNode>("input_gps_node", "/input/gps/fix", DELAY_JITTER_PAIR(7)); // Fast, low latency sensor
|
||||||
auto imu = std::make_shared<IMUNode>("imu_node", "/imu/data", 100);
|
auto imu = std::make_shared<IMUNode>("input_imu_node", "/input/imu/data", DELAY_JITTER_PAIR(5)); // Fast, low latency sensor
|
||||||
auto baro = std::make_shared<BaroNode>("baro_node", "/baro/alt", 20);
|
auto baro = std::make_shared<BaroNode>("input_baro_node", "/input/baro/alt", DELAY_JITTER_PAIR(6)); // Fast, low latency sensor
|
||||||
auto fusion = std::make_shared<FusionNode>(
|
auto fusion = std::make_shared<FusionNode>(
|
||||||
"sensor_fusion_node", std::vector<std::string>{"/gps/fix", "/imu/data", "/baro/alt"}, "/sensors/fused", 10);
|
"sensor_fusion_node", std::vector<std::string>{"/input/gps/fix", "/input/imu/data", "/input/baro/alt"}, "/sensors/fused", DELAY_JITTER_PAIR(14)); // Sensor fusion, not trivial but not heavy
|
||||||
auto lidar = std::make_shared<LidarNode>("lidar_node", "/lidar/scan", 15);
|
|
||||||
// TODO: operator or autonomous flight plan
|
auto lidar = std::make_shared<LidarNode>("input_lidar_node", "/input/lidar/scan", DELAY_JITTER_PAIR(10)); // LIDAR driver, can be fast
|
||||||
auto cmd = std::make_shared<CommandNode>("operator_cmd_node", "/operator/commands", 1);
|
auto cmd = std::make_shared<CommandNode>("input_operator_cmd_node", "/input/operator/commands", DELAY_JITTER_PAIR(8)); // Operator commands, fast
|
||||||
auto mgmt = std::make_shared<FlightManagementNode>(
|
auto mgmt = std::make_shared<FlightManagementNode>(
|
||||||
"flight_mgmt_node", std::vector<std::string>{"/sensors/fused", "/lidar/scan", "/operator/commands"}, "/flight/plan", 20);
|
"flight_mgmt_node", std::vector<std::string>{"/sensors/fused", "/input/lidar/scan", "/input/operator/commands"}, "/flight/plan", DELAY_JITTER_PAIR(18)); // Path planning, moderate
|
||||||
auto control = std::make_shared<ControlNode>("flight_control_node", "/flight/plan", "/flight/cmd", 25);
|
auto control = std::make_shared<ControlNode>("output_flight_control_node", "/flight/plan", "/output/flight/cmd", DELAY_JITTER_PAIR(12)); // PID controller, fast but non-zero
|
||||||
|
|
||||||
// Chain 4: Data Storage/Streaming
|
// Chain 4: Data Storage/Streaming
|
||||||
auto telemetry = std::make_shared<TelemetryNode>(
|
auto telemetry = std::make_shared<TelemetryNode>(
|
||||||
"telemetry_node", std::vector<std::string>{"/sensors/fused", "/camera/mapped"}, "/telemetry/data", 5);
|
"telemetry_node", std::vector<std::string>{"/sensors/fused", "/output/camera/mapped"}, "/telemetry/data", DELAY_JITTER_PAIR(6)); // Quick serialization
|
||||||
auto radio = std::make_shared<RadioNode>("radio_tx_node", "/telemetry/data", "/telemetry/radio", 10);
|
auto radio = std::make_shared<RadioNode>("output_radio_tx_node", "/telemetry/data", "/output/telemetry/radio", DELAY_JITTER_PAIR(3)); // Output, almost instant
|
||||||
|
|
||||||
// Add all to executor
|
// Add all to executor
|
||||||
executor.add_node(camera);
|
executor.add_node(camera);
|
||||||
|
@ -275,17 +218,21 @@ int main(int argc, char **argv)
|
||||||
executor.add_node(radiometric);
|
executor.add_node(radiometric);
|
||||||
executor.add_node(geometric);
|
executor.add_node(geometric);
|
||||||
executor.add_node(mapping);
|
executor.add_node(mapping);
|
||||||
|
|
||||||
executor.add_node(smoke);
|
executor.add_node(smoke);
|
||||||
|
|
||||||
executor.add_node(gps);
|
executor.add_node(gps);
|
||||||
executor.add_node(imu);
|
executor.add_node(imu);
|
||||||
executor.add_node(baro);
|
executor.add_node(baro);
|
||||||
executor.add_node(fusion);
|
executor.add_node(fusion);
|
||||||
|
|
||||||
|
executor.add_node(telemetry);
|
||||||
|
executor.add_node(radio);
|
||||||
|
|
||||||
executor.add_node(lidar);
|
executor.add_node(lidar);
|
||||||
executor.add_node(cmd);
|
executor.add_node(cmd);
|
||||||
executor.add_node(mgmt);
|
executor.add_node(mgmt);
|
||||||
executor.add_node(control);
|
executor.add_node(control);
|
||||||
executor.add_node(telemetry);
|
|
||||||
executor.add_node(radio);
|
|
||||||
|
|
||||||
executor.spin();
|
executor.spin();
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 1b9605494554d4d740d356efc43cf647c0fb9f66
|
Subproject commit e8637c9043b9bc396135dbfa7068143bfa8a879c
|
|
@ -1,55 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
|
||||||
project(simple_topology VERSION 0.1.0)
|
|
||||||
|
|
||||||
# Set C++ standards
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
|
|
||||||
# Compiler options
|
|
||||||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
||||||
add_compile_options(-Wall -Wextra -Wpedantic)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# find dependencies
|
|
||||||
find_package(ament_cmake REQUIRED)
|
|
||||||
find_package(tracetools REQUIRED)
|
|
||||||
find_package(rclcpp REQUIRED)
|
|
||||||
find_package(std_msgs REQUIRED)
|
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} src/simple_topology.cpp)
|
|
||||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
||||||
$<INSTALL_INTERFACE:include>
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add dependencies with ament
|
|
||||||
ament_target_dependencies(${PROJECT_NAME}
|
|
||||||
tracetools
|
|
||||||
rclcpp
|
|
||||||
std_msgs
|
|
||||||
)
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
install(
|
|
||||||
TARGETS ${PROJECT_NAME}
|
|
||||||
RUNTIME DESTINATION lib/${PROJECT_NAME}
|
|
||||||
)
|
|
||||||
install(
|
|
||||||
DIRECTORY launch
|
|
||||||
DESTINATION share/${PROJECT_NAME}
|
|
||||||
)
|
|
||||||
install(
|
|
||||||
DIRECTORY include/
|
|
||||||
DESTINATION include
|
|
||||||
)
|
|
||||||
|
|
||||||
# Testing
|
|
||||||
if(BUILD_TESTING)
|
|
||||||
find_package(ament_lint_auto REQUIRED)
|
|
||||||
ament_lint_auto_find_test_dependencies()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Export and package configuration
|
|
||||||
ament_export_include_directories(include)
|
|
||||||
ament_package()
|
|
|
@ -1,24 +0,0 @@
|
||||||
from launch import LaunchDescription
|
|
||||||
from launch_ros.actions import Node
|
|
||||||
from tracetools_launch.action import Trace
|
|
||||||
|
|
||||||
def generate_launch_description():
|
|
||||||
return LaunchDescription([
|
|
||||||
# Start tracing automatically
|
|
||||||
Trace(
|
|
||||||
session_name='ros2_tracing_session',
|
|
||||||
events_ust=[
|
|
||||||
'rclcpp:*', 'rcl:*', 'rmw:*', 'ros2:*', 'rclcpp:*callback*', 'rclcpp:*executor*'
|
|
||||||
],
|
|
||||||
events_kernel=[
|
|
||||||
'sched_switch', 'sched_wakeup'
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
# Your executable as a Node
|
|
||||||
Node(
|
|
||||||
package='simple_topology',
|
|
||||||
executable='simple_topology',
|
|
||||||
output='screen',
|
|
||||||
),
|
|
||||||
])
|
|
|
@ -1,36 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
|
||||||
<package format="3">
|
|
||||||
<name>simple_topology</name>
|
|
||||||
<version>0.1.0</version>
|
|
||||||
<description>TODO: Package description</description>
|
|
||||||
<maintainer email="niklas@niklashalle.net">dev</maintainer>
|
|
||||||
<license>TODO: License declaration</license>
|
|
||||||
|
|
||||||
<buildtool_depend>ament_cmake</buildtool_depend>
|
|
||||||
|
|
||||||
<depend>launch</depend>
|
|
||||||
<depend>launch_ros</depend>
|
|
||||||
<depend>tracetools</depend>
|
|
||||||
<depend>tracetools_trace</depend>
|
|
||||||
<depend>tracetools_launch</depend>
|
|
||||||
|
|
||||||
<!-- Build Dependencies -->
|
|
||||||
<depend>rclcpp</depend>
|
|
||||||
<depend>std_msgs</depend>
|
|
||||||
|
|
||||||
<test_depend>ament_copyright</test_depend>
|
|
||||||
<test_depend>ament_flake8</test_depend>
|
|
||||||
<test_depend>ament_mypy</test_depend>
|
|
||||||
<test_depend>ament_pep257</test_depend>
|
|
||||||
<test_depend>ament_xmllint</test_depend>
|
|
||||||
<test_depend>python3-pytest</test_depend>
|
|
||||||
|
|
||||||
<!-- Test Dependencies -->
|
|
||||||
<test_depend>ament_lint_auto</test_depend>
|
|
||||||
<test_depend>ament_lint_common</test_depend>
|
|
||||||
|
|
||||||
<export>
|
|
||||||
<build_type>ament_cmake</build_type>
|
|
||||||
</export>
|
|
||||||
</package>
|
|
|
@ -1,132 +0,0 @@
|
||||||
#include <chrono>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "rclcpp/rclcpp.hpp"
|
|
||||||
#include "std_msgs/msg/string.hpp"
|
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
|
||||||
|
|
||||||
constexpr int RUN_DURATION_SECONDS = 5;
|
|
||||||
|
|
||||||
// Nodes in the topology:
|
|
||||||
// - Chain 1: Image2Ground Mapping
|
|
||||||
// - Node 1: Camera Node (Sensor Node): Simulates a camera that publishes messages.
|
|
||||||
// - Node 2: De-Bayering Node (Processing Node): Processes the camera messages. Listens to Node 1.
|
|
||||||
// - Node 3: Radiometric Correction Node (Processing Node): Further processes the messages. Listens to Node 2.
|
|
||||||
// - Node 4: Geometric Correction Node (Processing Node): Applies geometric corrections. Listens to Node 3.
|
|
||||||
// - Node 5: Image to Ground Mapping Node (Processing Node): Maps the image to ground coordinates. Listens to Node 4.
|
|
||||||
// - Chain 2: Semantic Scheduler Adaptation
|
|
||||||
// - Node 6: Smoke/Fire Classification Node (Decision Node): Listens to Node 3 and makes scheduluer decisions based on the processed data.
|
|
||||||
// - Chain 3: Flight Control
|
|
||||||
// - Node 7: GPS Node (Sensor Node): Simulates a GPS sensor that publishes messages.
|
|
||||||
// - Node 8: IMU Node (Sensor Node): Simulates an IMU sensor that publishes messages.
|
|
||||||
// - Node 9: Barometric Altimeter Node (Sensor Node): Simulates a barometric altimeter that publishes messages.
|
|
||||||
// - Node 10: Sensor Fusion Node (Processing Node): Fuses data from the GPS, IMU, and barometric altimeter. Listens to Nodes 7, 8, and 9.
|
|
||||||
// - Node 11: LIDAR Node (Sensor Node): Simulates a LiDAR sensor that publishes messages.
|
|
||||||
// - Node 12: Operator Command Node (Sensor Node): Remote control or flight plan issuing node
|
|
||||||
// - Node 13: Flight Management Node (Processing Node): Manages flight operations. Listens to Node 10, 11, and 12.
|
|
||||||
// - Node 14: Flight Control Node (Processing Node): Controls the flight based on data from Node 13. Listens to Node 13.
|
|
||||||
// - Chain 4: Data Storage/Streaming
|
|
||||||
// - Node 15: Telemetry Node (Processing Node): Collects telemetry data. Listens to Node 10 and 5.
|
|
||||||
// - Node 16: Radio Transmitter Node (Processing Node): Transmits data. Listens to Node 15.
|
|
||||||
|
|
||||||
|
|
||||||
// --- Sensor Node ---
|
|
||||||
class SensorNode : public rclcpp::Node
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SensorNode() : Node("sensor_node")
|
|
||||||
{
|
|
||||||
publisher_ = this->create_publisher<std_msgs::msg::String>("sensor_topic", 10);
|
|
||||||
timer_ = this->create_wall_timer(
|
|
||||||
100ms, [this]() { publish_message(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void publish_message()
|
|
||||||
{
|
|
||||||
auto message = std_msgs::msg::String();
|
|
||||||
message.data = "Sensor reading";
|
|
||||||
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
|
|
||||||
publisher_->publish(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
|
|
||||||
rclcpp::TimerBase::SharedPtr timer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Processing Node ---
|
|
||||||
class ProcessingNode : public rclcpp::Node
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ProcessingNode() : Node("processing_node")
|
|
||||||
{
|
|
||||||
subscription_ = this->create_subscription<std_msgs::msg::String>(
|
|
||||||
"sensor_topic", 10,
|
|
||||||
[this](std_msgs::msg::String::SharedPtr msg) { process_message(msg); });
|
|
||||||
publisher_ = this->create_publisher<std_msgs::msg::String>("processed_topic", 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void process_message(const std_msgs::msg::String::SharedPtr msg)
|
|
||||||
{
|
|
||||||
RCLCPP_INFO(this->get_logger(), "Processing: '%s'", msg->data.c_str());
|
|
||||||
// Simulate processing delay
|
|
||||||
std::this_thread::sleep_for(10ms);
|
|
||||||
auto new_msg = std_msgs::msg::String();
|
|
||||||
new_msg.data = msg->data + " -> processed";
|
|
||||||
publisher_->publish(new_msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
|
|
||||||
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Decision Node ---
|
|
||||||
class DecisionNode : public rclcpp::Node
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DecisionNode() : Node("decision_node")
|
|
||||||
{
|
|
||||||
subscription_ = this->create_subscription<std_msgs::msg::String>(
|
|
||||||
"processed_topic", 10,
|
|
||||||
[this](std_msgs::msg::String::SharedPtr msg) { decide(msg); });
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void decide(const std_msgs::msg::String::SharedPtr msg)
|
|
||||||
{
|
|
||||||
RCLCPP_INFO(this->get_logger(), "Deciding based on: '%s'", msg->data.c_str());
|
|
||||||
// Future place for semantic detection (e.g., "if smoke detected, boost priority!")
|
|
||||||
}
|
|
||||||
|
|
||||||
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Main ---
|
|
||||||
int main(int argc, char * argv[])
|
|
||||||
{
|
|
||||||
rclcpp::init(argc, argv);
|
|
||||||
|
|
||||||
auto sensor_node = std::make_shared<SensorNode>();
|
|
||||||
auto processing_node = std::make_shared<ProcessingNode>();
|
|
||||||
auto decision_node = std::make_shared<DecisionNode>();
|
|
||||||
|
|
||||||
rclcpp::executors::MultiThreadedExecutor executor;
|
|
||||||
executor.add_node(sensor_node);
|
|
||||||
executor.add_node(processing_node);
|
|
||||||
executor.add_node(decision_node);
|
|
||||||
|
|
||||||
auto start_time = std::chrono::steady_clock::now();
|
|
||||||
|
|
||||||
while (rclcpp::ok() && (std::chrono::steady_clock::now() - start_time) < std::chrono::seconds(RUN_DURATION_SECONDS))
|
|
||||||
{
|
|
||||||
executor.spin_some();
|
|
||||||
std::this_thread::sleep_for(1ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
rclcpp::shutdown();
|
|
||||||
return 0;
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue