# ros2-interference **Repository Path**: jeffreysharp/ros2-interference ## Basic Information - **Project Name**: ros2-interference - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-11-25 - **Last Updated**: 2026-01-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 实验 ## 实验目的 检测ros2中一条链运行时 额外的节点(相同的订阅话题)对它执行延迟的干扰 ## 实验步骤 基础实验 测试不同数量的节点创建最坏情况下的耗时 令high = avg * 1.5 10 平均30ms 但是这没有其他的干扰 所以令high等于 50 20 平均60ms high=100ms 分别测试10个节点创建和20个节点创建时对延迟的影响 high = 50, 100ms exe1 = 150ms exe2 = 150ms 子实验1 主任务链共两个节点,分别为timer和sub 周期为T,二者执行时间为exe1 exe2 由于节点的创建耗时严重,于是为了对timer进行干扰 那么第二个的创建时间就为 [exe1+exe2+offset, T+exe1-offset] 创建耗时范围为[low, high] 那么应该有 T+exe1-offset - (exe1+exe2+offset) >= high T - exe2 - 2*offset >= high 令offset=110ms 则T >= 170 + high 同时T >= exe1+exe2 同时T <= high+exe1+exe2+offset T = 400ms sleep = T+exe1-offset-high = 390, 340 子实验2 主任务同样就两个节点 但是此时需要对后面的sub进行干扰 因此范围为 [exe1+offset, T-offset] 同样的,也应该有 T-offset - (exe1+offset) >= high T - exe1 - 2*offset >= high offset = 20ms 同理 T = 400ms sleep = exe1 + offset = 170 ## 实验结果 当前分为了四组实验 分别为 1.10个节点干扰timer 2.20个节点干扰timer 3.10个节点干扰sub 4.20个节点干扰sub 目前各种情况下的最坏时间为 600 300 450 300 可以看到,20个节点干扰,反而没有测出效果 # to-do fig_res analysis_log 等脚本的路径改成项目根路径和script路径都可用的状态 解决 src/other中不再是无穷创建,而是回归到周期创建 这个需要通过gene_verify_trigger_time.py来检验 三种干扰模式 分别是只创建一次 不停创建 和周期性创建 周期性创建负责干扰中间的执行时间 暂时看cpu利用率,不看延迟。 最开始的是创建周期200ms 三个10ms 这个没法周期性的,就一直创建就好了 这个的测试就分为如下情况 1.没有干扰的情况 对应verify_register_node_only_main 这个就是其余测试情况去掉第二个任务 2.有干扰 2.1 干扰任务的话题跟第一个任务的话题不相关 仍然存在影响 这个同样单独进行测试 测试结果单独保存 different_topic same_topic 2.2 干扰任务使用dds和不使用dds 不管是否使用都存在影响 这个单独进行测试 测试结果单独保存 use_dds not_use_dds 2.3 干扰任务的创建频率 2.3.1 不停创建 目前的都是不断创建的 所以只创建一次对比就是和不同的话题对比 2.3.2 只创建一次 只创建一次图中就应该只有一条线 因为默认用5条,只有一条,说明只运行了一次。 后续创建则使用不同的topic 不使用dds 测量topic时,默认是没有dds的,所以使用dds的对比实验就是相同和不同的topic use_intra_process_comms(true) ) 不使用dds use_intra_process_comms(false) ) 使用dds 解决 该配置的基础信息如下 默认创建10个节点 耗时大约为60ms+ 最终估算的时候就按70ms来 默认不使用dds 默认创建不同的topic T=600 EXE1=200 EXE2=150 EXE3=100 额外的等待时间还没有计算 其实也可以每次直接编译 另外的测试是长周期的,负责监控它在哪个时候创建的影响最大 两个任务,第一个任务的周期为T,两个执行时间分别为exe1和exe2 第二个任务的需要执行时间估计和执行周期 a 1.只创建一次 这个用于做基准测试 用于和后续的比较创建时间次数的干扰 因此它的sleep_time默认是0如果需要修改再和后面的保持一致 b 2.无限创建 c 3.在第一个运行时周期性创建 10 d 4.在第二个运行时周期性创建 210 这个实验需要注意的是 1.实验室电脑和本电脑 创建节点的运行耗时不一致 需要动态调整 通过check_cost_time.sh 每次计算之后确定结果 2.每次做完实验之后注意看一下脚本得出的延迟图是否符合预期 只创建一次注意时间的计算 做实验时先 1.固定cpu的频率 2.将sleep执行改成耗费cpu时间的执行 3.测试不同的主任务利用率的变化 4.测试干扰任务删除时是否会对主任务造成影响 # 项目的使用说明 ## 项目基础信息 默认包为ros2_interference 因此运行就是ros2 run ros2_interference 默认依赖包只有rclcpp和std_msgs 这些包只需要下载了最基础的ros2的就拥有了 ## 项目解释 最开始是打算使用launch文件解决启动问题的,但是使用launch文件不方便控制传递参数,开辟新线程,创建脚本等任务,因此使用的还是创建脚本的任务。 在根目录下存在三个shell脚本 分别用于编译这个项目 删除编译的结果 和激活环境 config_run verify_launch.json 该文件中有两个配置 verify_register_node 使用这个配置启动是有干扰存在的 主要看node_config中有几个节点以及它们对应的信息 verify_register_node_main 使用这个配置启动是没有干扰的 在src下的ros2_interference中有如下文件夹 include/ reference_system/ 这个文件夹负责提供测试用的节点 如sensor transform等 utils.hpp 这个文件负责提供工具函数 如等待到某个时间,获取当前时间 verify_system_builder.hpp 这个文件负责提供创建任务的函数 直接给它传递参数由它创建对应的任务 launch/ 已废弃 script/ 用于运行,分析,和绘图 config_fig/ 绘图用的配置文件夹 用于指示需要分析哪条链 experimental_result/ 用于保存必要实验结果的文件夹 其他实验结果输出的文件夹默认情况下git不会跟踪 fig/ 绘制出的图所在的文件夹 result/ json格式的结果 table/ csv和png格式的表格 trace/ 任务运行保存的默认文件夹 trace_cpu/ 记录cpu利用率的文件夹 analysis_log.py 分析trace中日志的脚本 fig_res.py 读取result中的res绘制结果的脚本 gene_verify_trigger_time.py 分析trace中日志制作成时序图的脚本 run_verify.py 从config_run中读取运行配置 使用命令行参数默认启动脚本,并将结果默认保存在trace文件夹 创建的时候会读取launch文件的配置 除了trace_path以外,其余都是直接传递给任务的 trace_path会在-a这种类别前添加它们需要睡眠到的时间作为时间戳 src/ verify_function.cpp 用于测试功能的文件 verify_register_node_main.cpp 主任务 verify_register_node_other_once.cpp 干扰任务,但只实例化一次 verify_register_node_other.cpp 干扰任务,周期性的实例化多次(但目前是直接无限运行) ## 快速导引 随着脚本数量越来越多,有点难以理解该如何进行操作,因此在此进行说明 首先说明当前的脚本类型 当前的脚本分为几个大类 1.和运行任务相关的脚本 run_verify.py 运行任务的脚本 配置在config_run中 split_trace.py 分割trace的脚本 因为目前我是默认在log中添加了时间戳,也会输出在trace中,所以需要进行分割 2.监控任务运行过程中参数的脚本 cpu_stat.sh 监控cpu使用情况的脚本 getpid.sh 获取当前进程的pid的脚本 是其他不少shell脚本的底层逻辑 monitor_cs.sh 监控context switch的脚本 monitor_cs_perf.sh 使用perf命令监控context switch的脚本 3.分析log的脚本 analysis_log.py 分析log的脚本 主要服务于trace文件夹中的日志 analysis_cpu_cs.py 分析cpu_cs.log的脚本 analysis_cpu_cs_perf.py 分析perf得到的结果 analyze_node_creation.py 这个是分析和绘制一体的脚本 主要服务于trace_create文件夹中的日志 analyze_trace_create.py 这个是分析和绘制一体的脚本 主要服务于trace_create文件夹中的日志 这两个脚本的核心区别就是 create中用来分析的脚本添加了更细节的log 4.根据分析后结果进行绘图的脚本 fig_cpu_cs_perf.py 绘制使用perf命令得到的context_switch结果的脚本 fig_cpu_cs.py 绘制使用ps命令得到的context_switch结果的脚本 fig_res.py 绘制result中的结果的脚本 是最早的一个绘制脚本 功能比较多,但是现在更多是一个实验配置一个对应的脚本来进行绘制,已不怎么使用 plot_cpu_usage.py 绘制cpu使用情况的脚本 这个是分析和绘制一体的脚本,主要服务于cpu_usage_{tag}.log plot_verification_metrics.py 绘制验证结果的脚本 主要服务于trace文件夹中的日志 同样也是一个分析和绘制一体的脚本 主要服务于trace文件夹中的日志 5.进行系统配置的脚本 cpu_freq.sh 配置cpu频率的脚本 6.废弃的脚本 check_cost_time.sh 当时用于快速启动脚本,现在已不怎么使用 clear.sh 当时用于快速清除编译结果, 现在更多是手动进行操作 cpu_usage_analysis.py 当时用于分析cpu使用情况的脚本,最开始是想单独进行使用的,但是后续融合到了analysis_log.py中 gene_report.py 当时用于生成报告的脚本,但是现在的结果比较复杂,较难去进行网页的展示,因此废弃 kill_cpu_task.sh 当时用于杀死特定CPU上所有任务的脚本,主要为了隔离环境 save_experiment_result.sh 当时用于保存实验结果的脚本,现在已不怎么使用 基本的使用方法 先通过run_verify.py运行任务 然后通过split_trace.py分割trace 接着找一下自己需要分析的trace文件要用的脚本(主要看日志) 然后使用对应脚本进行分析和绘制 # 使用gdb debug ```shell ros2 run --prefix 'gdb -ex run --args' ros2_interference verify_register_node_other_period -a 1735285297 -b 1735285297 -c 1735285297 -d 1735285297 ``` ```shell ros2 run --prefix 'gdb -ex run --args' ros2_interference verify_register_node_other_period trace/verify_register_node_other1735286028-a.log VerifySensor1 10 1000 50 1735286028 10000 1020 ``` # 进程数量分析 ### 源代码关键步骤 ```cpp int main(int argc, char* argv[]) { pid_t currentPid = getpid(); system(("pstree -pa " + std::to_string(currentPid)).c_str()); // 步骤1: 1个进程 dummy_load_calibration(); rclcpp::init(argc, argv); system(("pstree -pa " + std::to_string(currentPid)).c_str()); // 步骤2: 2个线程(主进程+1线程) int node_numbers = 10; // 创建节点 { std::vector> nodes; for (int i = 0; i < node_numbers; ++i) { nodes.emplace_back(/* ... */); // 步骤3.0 3.1 3.2: 10个线程(主进程+9线程) system(("pstree -pa " + std::to_string(currentPid)).c_str()); } nodes.clear(); // 步骤4: 3个线程(主进程+2线程) system(("pstree -pa " + std::to_string(currentPid)).c_str()); } system(("pstree -pa " + std::to_string(currentPid)).c_str()); // 步骤5: 3个线程(主进程+2线程) for (int i = 0; i < 10; ++i) { system(("pstree -pa " + std::to_string(currentPid)).c_str()); // 步骤6.1.0 6.2.0 6.3.0: 3个线程(主进程+2线程) std::vector> nodes; for (int i = 0; i < node_numbers; ++i) { nodes.emplace_back(/* ... */); } system(("pstree -pa " + std::to_string(currentPid)).c_str()); // 步骤6.1.1 6.2.1 6.3.1: 10个线程(主进程+9线程) nodes.clear(); system(("pstree -pa " + std::to_string(currentPid)).c_str()); // 步骤6.1.2 6.2.2 6.3.2: 3个线程(主进程+2线程) } system(("pstree -pa " + std::to_string(currentPid)).c_str()); // 步骤7: 3个线程(主进程+2线程) rclcpp::shutdown(); system(("pstree -pa " + std::to_string(currentPid)).c_str()); // 步骤8: 2个线程(主进程+1线程) } ``` ### Shell输出日志(添加步骤标注) ```shell # 步骤1: 1个进程 verify_thread,29556 └─sh,29557 -c pstree -pa 29556 └─pstree,29558 -pa 29556 # 步骤2: rclcpp::init 后 主进程+1线程 verify_thread,29556 ├─sh,29589 -c pstree -pa 29556 │ └─pstree,29590 -pa 29556 └─{verify_thread},29588 # 步骤3.0: 创建节点 主进程+9线程 verify_thread,29556 ├─sh,29599 -c pstree -pa 29556 │ └─pstree,29600 -pa 29556 ├─{verify_thread},29588 ├─{verify_thread},29591 ├─{verify_thread},29592 ├─{verify_thread},29593 ├─{verify_thread},29594 ├─{verify_thread},29595 ├─{verify_thread},29596 ├─{verify_thread},29597 └─{verify_thread},29598 # 步骤3.1: 10个线程(主进程+9线程) verify_thread,29556 ├─sh,29601 -c pstree -pa 29556 │ └─pstree,29602 -pa 29556 ├─{verify_thread},29588 ├─{verify_thread},29591 ├─{verify_thread},29592 ├─{verify_thread},29593 ├─{verify_thread},29594 ├─{verify_thread},29595 ├─{verify_thread},29596 ├─{verify_thread},29597 └─{verify_thread},29598 # 步骤3.2: 10个线程(主进程+9线程) verify_thread,29556 ├─sh,29603 -c pstree -pa 29556 │ └─pstree,29604 -pa 29556 ├─{verify_thread},29588 ├─{verify_thread},29591 ├─{verify_thread},29592 ├─{verify_thread},29593 ├─{verify_thread},29594 ├─{verify_thread},29595 ├─{verify_thread},29596 ├─{verify_thread},29597 └─{verify_thread},29598 # 步骤4: 清理节点后 3个线程(主进程+2线程) verify_thread,29556 ├─sh,29605 -c pstree -pa 29556 │ └─pstree,29606 -pa 29556 ├─{verify_thread},29588 └─{verify_thread},29591 # 步骤5: 3个线程(主进程+2线程) verify_thread,29556 ├─sh,29607 -c pstree -pa 29556 │ └─pstree,29608 -pa 29556 ├─{verify_thread},29588 └─{verify_thread},29591 # 步骤6.1.0: 创建节点 3个线程(主进程+2线程) verify_thread,29556 ├─sh,29609 -c pstree -pa 29556 │ └─pstree,29610 -pa 29556 ├─{verify_thread},29588 └─{verify_thread},29591 # 步骤6.1.1: 创建节点 10个线程(主进程+9线程) verify_thread,29556 ├─sh,29629 -c pstree -pa 29556 │ └─pstree,29630 -pa 29556 ├─{verify_thread},29588 ├─{verify_thread},29591 ├─{verify_thread},29621 ├─{verify_thread},29622 ├─{verify_thread},29623 ├─{verify_thread},29624 ├─{verify_thread},29625 ├─{verify_thread},29626 └─{verify_thread},29627 # 步骤6.1.2: 清理节点 3个线程(主进程+2线程) verify_thread,29556 ├─sh,29633 -c pstree -pa 29556 │ └─pstree,29634 -pa 29556 ├─{verify_thread},29588 └─{verify_thread},29591 # 步骤6.2.0: 创建节点 3个线程(主进程+2线程) verify_thread,29556 ├─sh,29635 -c pstree -pa 29556 │ └─pstree,29636 -pa 29556 ├─{verify_thread},29588 └─{verify_thread},29591 # 步骤6.2.1: 创建节点 10个线程(主进程+9线程) verify_thread,29556 ├─sh,29644 -c pstree -pa 29556 │ └─pstree,29645 -pa 29556 ├─{verify_thread},29588 ├─{verify_thread},29591 ├─{verify_thread},29637 ├─{verify_thread},29638 ├─{verify_thread},29639 ├─{verify_thread},29640 ├─{verify_thread},29641 ├─{verify_thread},29642 └─{verify_thread},29643 # 步骤6.2.2: 清理节点 3个线程(主进程+2线程) verify_thread,29556 ├─sh,29646 -c pstree -pa 29556 │ └─pstree,29647 -pa 29556 ├─{verify_thread},29588 └─{verify_thread},29591 # 步骤6.3.0: 清理节点 3个线程(主进程+2线程) verify_thread,29556 ├─sh,29648 -c pstree -pa 29556 │ └─pstree,29649 -pa 29556 ├─{verify_thread},29588 └─{verify_thread},29591 # 步骤6.3.1: 创建节点 10个线程(主进程+9线程) verify_thread,29556 ├─sh,29657 -c pstree -pa 29556 │ └─pstree,29658 -pa 29556 ├─{verify_thread},29588 ├─{verify_thread},29591 ├─{verify_thread},29650 ├─{verify_thread},29651 ├─{verify_thread},29652 ├─{verify_thread},29653 ├─{verify_thread},29654 ├─{verify_thread},29655 └─{verify_thread},29656 # 步骤6.3.2: 清理节点 3个线程(主进程+2线程) verify_thread,29556 ├─sh,29659 -c pstree -pa 29556 │ └─pstree,29660 -pa 29556 ├─{verify_thread},29588 └─{verify_thread},29591 # 步骤7: 3个线程(主进程+2线程) verify_thread,29556 ├─sh,29661 -c pstree -pa 29556 │ └─pstree,29662 -pa 29556 ├─{verify_thread},29588 └─{verify_thread},29591 # 步骤8: 2个线程(主进程+1线程) verify_thread,29556 ├─sh,29663 -c pstree -pa 29556 │ └─pstree,29664 -pa 29556 └─{verify_thread},29591 ``` ### 执行结果分析(不包含临时的 sh 和 pstree 进程) 1. 步骤1: 主进程启动 - 进程/线程总数: 1 - 内容: verify_thread 主进程 2. 步骤2: rclcpp::init 后 - 进程/线程总数: 2 - 内容: verify_thread 主进程 + 1个线程 3. 步骤3: 创建ROS2节点过程 - 进程/线程总数: 10 - 内容: verify_thread 主进程 + 9个线程 - 说明: 每创建一个节点会产生额外的线程 4. 步骤4: 清理节点后 - 进程/线程总数: 10 - 内容: 保持不变 5. 步骤5: 作用域结束后 - 进程/线程总数: 10 - 内容: 保持不变 6. 步骤6循环中: - 6.1: 循环开始时 - 进程/线程总数: 2 - 内容: verify_thread 主进程 + 1个线程 - 6.2: 创建新节点后 - 进程/线程总数: 10 - 内容: verify_thread 主进程 + 9个线程 - 6.3: 清理节点后 - 进程/线程总数: 2 - 内容: verify_thread 主进程 + 1个线程 7. 步骤7: 循环结束后 - 进程/线程总数: 2 - 内容: verify_thread 主进程 + 1个线程 8. 步骤8: rclcpp::shutdown 后 - 进程/线程总数: 1 - 内容: verify_thread 主进程 ### 观察结论 1. ROS2 初始化会创建一个基础线程 2. 每创建一组节点会产生额外的线程(约8-9个) 3. 节点清理后,额外的线程会被回收 4. 最终关闭时,所有额外的线程都会被清理 ## 创建耗时 ```shell ros2 run ros2_interference verify_thread_create_first 2 100 > thread_create_frist.log ros2 run ros2_interference verify_thread 2 100 > thread.log ``` ```shell taskset 1 ros2 run ros2_interference verify_thread_create_first 20 10 > thread_create_first.log taskset 1 ros2 run ros2_interference verify_thread_create_first 2 100 > thread_create_first.log taskset 1 ros2 run ros2_interference verify_thread_create_first 1 200 > thread_create_first.log ``` ```shell ros2 run ros2_interference verify_register_node_main trace/verify_register_node1736833155-a.log 3 200 VerifySensor 30 VerifyTransform 20 VerifyCommand 10 ``` # 节点数量会影响同一个domain还是同一个线程 ## 实验设计 实验目的:探索是在同一个domain中还是在同一个进程中的同时拥有的节点个数会影响node的创建时间。 预期结果:应该是在同一个进程中,但是也可以额外测试一下,是否有额外的增长,我估计是有的。应该不会像之前那样增长的很恐怖,但是应该是会有一定的增长的。 第一组实验 主任务一直在创建node,干扰任务则先创建一个,然后循环创建1个,监控这一个的时间是否随着主任务的创建耗时而增加。如果存在,那么就要去搞清楚到底是因为哪个部分的耗时而在增加。 第二组实验 主任务先创建n个node,开始运行之后,再由干扰任务去创建node,去监控这样的时间。 监控的指标是node的创建耗时,对应任务的cs次数 ## 如何测量 这里仅仅对如何使用代码进行监控进行说明 首先简单说明一下我是如何实现的第一组和第二组实验所需要的功能 对于第一组和第二组实验来说,我都创建了一个node,作为最开始创建的节点。 然后对第一组实验,即meanwhile, 同时运行的版本,我创建完这个节点之后就直接开始循环创建1个node,来监控延迟。 但是对于第二组实验,即wait,等待的版本,我额外创建了一个executor, 使用这个executor来add_node的这个节点,然后让这个节点订阅了主任务的第一个publisher。然后使用spin_some一直循环等待到主任务的节点全部创建完毕然后开始运行。这样程序才会继续向下走,去执行循环创建1个node。 该实验的配置在config_run的verify_launch_property.json中的verify_register_node_main_multinode里面。 -a版本对应的是第一组实验,-b版本对应的是第二组实验。 接着使用run_verify.py来运行实验。 注意本实验默认humble中存在一些log代码,用来打印出创建node中的耗时。 运行完实验后,再使用split_trace_create.py来分离这样的log代码和原先在代码中的printf输出。 这样一部分用于分析节点的创建耗时,一部分用于承接之前的接口。 之后再使用analyze_cpu_cs.py 和 fig_cpu_cs.py来得到最终的context switch图。注意,这里我有两种坐标轴的结果,一种是对数,一种是线性的。 对于节点创建耗时,则直接使用analyze_trace_create.py来分析。最终得到的两个图,一个侧重于细节,另外一个则添加了一些统计信息。 # sros ```shell # 创建一个新的密钥库 ros2 security create_keystore my_security_keystore # 使用generate_artifacts创建所有安全工件 ros2 security generate_artifacts -k my_security_keystore -p policy.xml ``` ```shell export ROS_SECURITY_ENABLE=true export ROS_SECURITY_KEYSTORE=~/Codes/ros2-interference/my_security_keystore export ROS_SECURITY_STRATEGY=Enforce ``` ```shell ros2 run ros2_interference verify_register_node_main --ros-args --enclave / ``` ```shell lucifer@luciferLinux:~/Codes/ros2-interference/src/ros2_interference/script$ taskset -a -c 0-0 ros2 run ros2_interference verify_register_node_main trace/verify_register_node-a.log 3 200 VerifySensor 30 VerifyTransform 20 VerifyCommand 10 1744605523 --ros-args --enclave / [INFO] [1744609528.302008177] [rcl]: Found security directory: /home/lucifer/Codes/ros2-interference/my_security_keystore/enclaves main[1744609528306770901]: node_names:[VerifySensor] node_exes:[30] main[1744609528306802720]: node_names:[VerifyTransform] node_exes:[20] main[1744609528306806312]: node_names:[VerifyCommand] node_exes:[10] main[29945][1744609528306814955]: trace_path:[trace/verify_register_node-a.log] sleep_time:[1744605523] ^C[INFO] [1744609805.817027258] [rclcpp]: signal_handler(signum=2) lucifer@luciferLinux:~/Codes/ros2-interference/src/ros2_interference/script$ unset ROS_SECURITY_ENABLE lucifer@luciferLinux:~/Codes/ros2-interference/src/ros2_interference/script$ taskset -a -c 0-0 ros2 run ros2_interference verify_register_node_main trace/verify_register_node-a.log 3 200 VerifySensor 30 VerifyTransform 20 VerifyCommand 10 1744605523 main[1744609945005499930]: node_names:[VerifySensor] node_exes:[30] main[1744609945005515625]: node_names:[VerifyTransform] node_exes:[20] main[1744609945005516859]: node_names:[VerifyCommand] node_exes:[10] main[31395][1744609945005521976]: trace_path:[trace/verify_register_node-a.log] sleep_time:[1744605523] ^C[INFO] [1744609964.582551546] [rclcpp]: signal_handler(signum=2) ```