调试
Holoscan SDK 旨在简化开发者处理高级应用程序的调试流程。
本综合指南涵盖了 SDK 的调试功能,重点介绍了 Visual Studio Code 集成,并为各种调试场景提供了详细说明。
它包括调试应用程序的 C++ 和 Python 组件的方法,利用 GDB、UCX 和 Python 特定的调试器等工具。
VSCode 开发容器
可以使用 Visual Studio Code 有效地开发 Holoscan SDK,利用开发容器的功能。此容器在 .devcontainer
文件夹中定义,预配置了所有必要的工具和库,详情请参阅 Visual Studio Code 关于开发容器的文档。
使用 Holoscan SDK 启动 VSCode
本地开发:使用
./run vscode
命令在开发容器中启动 Visual Studio Code(可以使用-j <# of workers>
或--parallel <# of workers>
指定构建过程中要运行的并行作业数)。有关更多信息,请参阅./run vscode -h
中的说明。远程开发:要从远程计算机连接到现有开发容器,请使用
./run vscode_remote
。可以通过./run vscode_remote -h
访问其他说明。
启动 Visual Studio Code 后,将自动构建开发容器。此过程还包括安装推荐的扩展和配置 CMake。
配置 CMake
对于 CMake 配置的手动调整
在 VSCode 中打开命令面板 (
Ctrl + Shift + P
)。执行
CMake: Configure
命令。
构建源代码
要在开发容器中构建源代码
按
Ctrl + Shift + B
。或使用命令面板 (
Ctrl + Shift + P
) 并运行Tasks: Run Build Task
。
调试工作流程
要调试源代码
在 VSCode 中打开
Run and Debug
视图 (Ctrl + Shift + D
)。从下拉列表中选择合适的调试配置。
按
F5
启动调试会话。
启动配置在 .vscode/launch.json
中定义(链接)。
有关更多信息,请参阅 Visual Studio Code 关于调试的文档。
Holoscan SDK 中 C++ 和 Python 的集成调试
Holoscan SDK 方便您在应用程序中无缝调试 C++ 和 Python 组件。这是通过集成 Visual Studio Code 中的 Python C++ Debugger
扩展实现的,可以在这里找到。
这个强大的扩展程序专门用于对在 C++ 运行时环境中执行的 Python 运算符进行有效调试。此外,它还为调试 C++ 运算符和通过 Python 解释器执行的各种 SDK 组件提供了强大的功能。
要使用此功能,应在 .vscode/launch.json
文件中定义 Python C++ Debug
的调试配置,可在此处找到。
以下是入门方法
在您的项目中打开一个 Python 文件,例如
examples/ping_vector/python/ping_vector.py
。在 Visual Studio Code 的
Run and Debug
视图中,选择Python C++ Debug
调试配置。在您的 Python 和 C++ 代码中设置必要的断点。
按
F5
启动调试会话。
会话启动后,将启动两个独立的调试终端;一个用于 Python,另一个用于 C++。在 C++ 终端中,您将遇到有关超级用户访问的提示
Superuser access is required to attach to a process. Attaching as superuser can potentially harm your computer. Do you want to continue? [y/N]
回复 y
以继续。
在此之后,Python 应用程序启动,C++ 调试器附加到 Python 进程。此设置允许您同时调试 Python 和 C++ 代码。Run and Debug
视图中的 CALL STACK
选项卡将显示 Python: Debug Current File
和 (gdb) Attach
,指示两种语言的活动调试会话。
通过利用这种集成的调试方法,开发人员可以有效地排除故障并增强在 Holoscan SDK 中同时使用 Python 和 C++ 组件的应用程序。
本节概述了调试应用程序崩溃的步骤。
核心转储分析
如果应用程序崩溃,您可能会遇到类似 Segmentation fault (core dumped)
或 Aborted (core dumped)
的消息。这些消息表明生成了核心转储文件,该文件捕获了应用程序崩溃时的内存状态。此文件可用于调试目的。
启用核心转储
在某些情况下,即使应用程序崩溃,核心转储也可能被禁用或未生成。
要激活核心转储,需要配置 ulimit
设置,该设置确定核心转储文件的最大大小。默认情况下,ulimit
设置为 0,实际上禁用了核心转储。将 ulimit
设置为 unlimited 将启用核心转储的生成。
ulimit -c unlimited
此外,还需要配置 core_pattern
值。此值指定核心转储文件的命名约定。要查看当前的 core_pattern
设置,请执行以下命令
cat /proc/sys/kernel/core_pattern
# or
sysctl kernel.core_pattern
要修改 core_pattern
值,请执行以下命令
echo "coredump_%e_%p" | sudo tee /proc/sys/kernel/core_pattern
# or
sudo sysctl -w kernel.core_pattern=coredump_%e_%p
在本例中,我们已请求在生成的文件名中同时包含可执行文件名 (%e
) 和进程 ID (%p
)。核心文档中记录了各种可用选项。
如果您在 Docker 容器中遇到类似 tee: /proc/sys/kernel/core_pattern: Read-only file system
或 sysctl: setting key "kernel.core_pattern", ignoring: Read-only file system
的错误,建议在主机系统上而不是在容器内设置 kernel.core_pattern
参数。
由于 kernel.core_pattern
是系统范围的内核参数,因此在主机上修改它应该会影响所有容器。但是,此方法需要在主机上具有适当的权限。
此外,当使用 docker run
启动 Docker 容器时,通常必须包含 --cap-add=SYS_PTRACE
选项才能在容器内启用核心转储创建。核心转储生成通常需要提升的权限,而 Docker 容器默认情况下不具备这些权限。
使用 GDB 调试核心转储文件
生成核心转储文件后,您可以使用 GDB 调试核心转储文件。
考虑这样一种情况:通过在 examples/ping_simple/cpp/ping_simple.cpp
的第 29 行添加行 *(int*)0 = 0;
来故意引发段错误,以触发故障。
--- a/examples/ping_simple/cpp/ping_simple.cpp
+++ b/examples/ping_simple/cpp/ping_simple.cpp
@@ -19,7 +19,6 @@
#include <holoscan/operators/ping_tx/ping_tx.hpp>
#include <holoscan/operators/ping_rx/ping_rx.hpp>
-
class MyPingApp : public holoscan::Application {
public:
void compose() override {
@@ -27,6 +26,7 @@ class MyPingApp : public holoscan::Application {
// Define the tx and rx operators, allowing the tx operator to execute 10 times
auto tx = make_operator<ops::PingTxOp>("tx", make_condition<CountCondition>(10));
auto rx = make_operator<ops::PingRxOp>("rx");
+ *(int*)0 = 0;
运行 ./examples/ping_simple/cpp/ping_simple
后,观察到以下输出
$ ./examples/ping_simple/cpp/ping_simple
Segmentation fault (core dumped)
很明显,应用程序已中止,并且已生成核心转储文件。
$ ls coredump*
coredump_ping_simple_2160275
可以使用 GDB 通过执行 gdb <application> <coredump_file>
来调试核心转储文件。
$ gdb ./examples/ping_simple/cpp/ping_simple coredump_ping_simple_2160275
给出
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://gnu.ac.cn/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<https://gnu.ac.cn/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./examples/ping_simple/cpp/ping_simple...
[New LWP 2160275]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `./examples/ping_simple/cpp/ping_simple'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 MyPingApp::compose (this=0x563bd3a3de80) at ../examples/ping_simple/cpp/ping_simple.cpp:29
29 *(int*)0 = 0;
(gdb)
很明显,应用程序在 examples/ping_simple/cpp/ping_simple.cpp
的第 29 行崩溃。
要显示回溯,可以执行 bt
命令。
(gdb) bt
#0 MyPingApp::compose (this=0x563bd3a3de80) at ../examples/ping_simple/cpp/ping_simple.cpp:29
#1 0x00007f2a76cdb5ea in holoscan::Application::compose_graph (this=0x563bd3a3de80) at ../src/core/application.cpp:325
#2 0x00007f2a76c3d121 in holoscan::AppDriver::check_configuration (this=0x563bd3a42920) at ../src/core/app_driver.cpp:803
#3 0x00007f2a76c384ef in holoscan::AppDriver::run (this=0x563bd3a42920) at ../src/core/app_driver.cpp:168
#4 0x00007f2a76cda70c in holoscan::Application::run (this=0x563bd3a3de80) at ../src/core/application.cpp:207
#5 0x0000563bd2ec4002 in main (argc=1, argv=0x7ffea82c4c28) at ../examples/ping_simple/cpp/ping_simple.cpp:38
UCX 段错误处理程序
在使用 UCX 库的分布式应用程序遇到段错误的情况下,您可能会看到来自 UCX 的堆栈跟踪。这是 UCX 库的默认配置,用于在发生段错误时输出堆栈跟踪。但是,可以通过设置 UCX_HANDLE_ERRORS
环境变量来修改此行为
UCX_HANDLE_ERRORS=bt
在段错误期间打印回溯(默认设置)。UCX_HANDLE_ERRORS=debug
如果发生段错误,则附加调试器。UCX_HANDLE_ERRORS=freeze
在段错误时冻结应用程序。UCX_HANDLE_ERRORS=freeze,bt
在段错误时同时冻结应用程序并打印回溯。UCX_HANDLE_ERRORS=none
在段错误期间禁用回溯打印。
虽然默认操作是在段错误时打印回溯,但这可能并不总是有帮助的。
例如,如果在 /workspace/holoscan-sdk/src/operators/ping_tensor_tx/ping_tensor_tx.cpp
中的 PingTensorTxOp::compute
的开头附近的第 139 行故意引起段错误(通过添加 *(int*)0 = 0;
),运行 ./examples/ping_distributed/cpp/ping_distributed
将导致以下输出
[holoscan:2097261:0:2097311] Caught signal 11 (Segmentation fault: address not mapped to object at address (nil))
==== backtrace (tid:2097311) ====
0 /opt/ucx/1.15.0/lib/libucs.so.0(ucs_handle_error+0x2e4) [0x7f18db865264]
1 /opt/ucx/1.15.0/lib/libucs.so.0(+0x3045f) [0x7f18db86545f]
2 /opt/ucx/1.15.0/lib/libucs.so.0(+0x30746) [0x7f18db865746]
3 /usr/lib/x86_64-linux-gnu/libc.so.6(+0x42520) [0x7f18da9ee520]
4 ./examples/ping_distributed/cpp/ping_distributed(+0x103d2b) [0x5651dafc7d2b]
5 /workspace/holoscan-sdk/build-debug-x86_64/lib/libholoscan_core.so.1(_ZN8holoscan3gxf10GXFWrapper4tickEv+0x13d) [0x7f18dcbfaafd]
6 /workspace/holoscan-sdk/build-debug-x86_64/lib/libgxf_core.so(_ZN6nvidia3gxf14EntityExecutor10EntityItem11tickCodeletERKNS0_6HandleINS0_7CodeletEEE+0x127) [0x7f18db2cb487]
7 /workspace/holoscan-sdk/build-debug-x86_64/lib/libgxf_core.so(_ZN6nvidia3gxf14EntityExecutor10EntityItem4tickElPNS0_6RouterE+0x444) [0x7f18db2cde44]
8 /workspace/holoscan-sdk/build-debug-x86_64/lib/libgxf_core.so(_ZN6nvidia3gxf14EntityExecutor10EntityItem7executeElPNS0_6RouterERl+0x3e9) [0x7f18db2ce859]
9 /workspace/holoscan-sdk/build-debug-x86_64/lib/libgxf_core.so(_ZN6nvidia3gxf14EntityExecutor13executeEntityEll+0x41b) [0x7f18db2cf0cb]
10 /workspace/holoscan-sdk/build-debug-x86_64/lib/libgxf_serialization.so(_ZN6nvidia3gxf20MultiThreadScheduler20workerThreadEntranceEPNS0_10ThreadPoolEl+0x3c0) [0x7f18daf0cc50]
11 /usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0xdc253) [0x7f18dacb0253]
12 /usr/lib/x86_64-linux-gnu/libc.so.6(+0x94ac3) [0x7f18daa40ac3]
13 /usr/lib/x86_64-linux-gnu/libc.so.6(+0x126660) [0x7f18daad2660]
=================================
Segmentation fault (core dumped)
虽然提供了回溯,但它可能并不总是有帮助,因为它通常缺少源代码信息。为了获得详细的源代码信息,必须使用调试器。
通过将 UCX_HANDLE_ERRORS
环境变量设置为 freeze,bt
并运行 ./examples/ping_distributed/cpp/ping_distributed
,我们可以观察到负责段错误的线程被冻结,从而允许我们将调试器附加到它以进行进一步调查。
$ UCX_HANDLE_ERRORS=freeze,bt ./examples/ping_distributed/cpp/ping_distributed
[holoscan:37 :1:51] Caught signal 11 (Segmentation fault: address not mapped to object at address (nil))
==== backtrace (tid: 51) ====
0 /opt/ucx/1.15.0/lib/libucs.so.0(ucs_handle_error+0x2e4) [0x7f9fc6d75264]
1 /opt/ucx/1.15.0/lib/libucs.so.0(+0x3045f) [0x7f9fc6d7545f]
2 /opt/ucx/1.15.0/lib/libucs.so.0(+0x30746) [0x7f9fc6d75746]
3 /usr/lib/x86_64-linux-gnu/libc.so.6(+0x42520) [0x7f9fc803e520]
4 /workspace/holoscan-sdk/build-x86_64/lib/libholoscan_op_ping_tensor_tx.so.2(_ZN8holoscan3ops14PingTensorTxOp7computeERNS_12InputContextERNS_13OutputContextERNS_16ExecutionContextE+0x53) [0x7f9fcad9e7f1]
5 /workspace/holoscan-sdk/build-x86_64/lib/libholoscan_core.so.2(_ZN8holoscan3gxf10GXFWrapper4tickEv+0x155) [0x7f9fc9e415eb]
6 /workspace/holoscan-sdk/build-x86_64/lib/libgxf_sample.so(_ZN6nvidia3gxf14EntityExecutor10EntityItem11tickCodeletERKNS0_6HandleINS0_7CodeletEEE+0x1a7) [0x7f9fc88f0347]
7 /workspace/holoscan-sdk/build-x86_64/lib/libgxf_sample.so(_ZN6nvidia3gxf14EntityExecutor10EntityItem4tickElPNS0_6RouterE+0x460) [0x7f9fc88f29c0]
8 /workspace/holoscan-sdk/build-x86_64/lib/libgxf_sample.so(_ZN6nvidia3gxf14EntityExecutor10EntityItem7executeElPNS0_6RouterERl+0x31e) [0x7f9fc88f31ee]
9 /workspace/holoscan-sdk/build-x86_64/lib/libgxf_sample.so(_ZN6nvidia3gxf14EntityExecutor13executeEntityEll+0x2e7) [0x7f9fc88f39d7]
10 /workspace/holoscan-sdk/build-x86_64/lib/libgxf_serialization.so(_ZN6nvidia3gxf20MultiThreadScheduler20workerThreadEntranceEPNS0_10ThreadPoolEl+0x419) [0x7f9fc8605dd9]
11 /usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0xdc253) [0x7f9fc8321253]
12 /usr/lib/x86_64-linux-gnu/libc.so.6(+0x94ac3) [0x7f9fc8090ac3]
13 /usr/lib/x86_64-linux-gnu/libc.so.6(clone+0x44) [0x7f9fc8121a04]
=================================
[holoscan:2127091:0:2127105] Process frozen, press Enter to attach a debugger...
观察到负责段错误的线程是 51 (tid: 51
)。要将调试器附加到此线程,只需按 Enter 键。
附加调试器后,将显示回溯,但它可能不是来自触发段错误的线程。要处理此问题,请使用 info threads
命令列出所有线程,并使用 thread <thread_id>
命令切换到导致段错误的线程。
(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7f9fc6ce2000 (LWP 37) "ping_distribute" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
2 Thread 0x7f9fc51bb000 (LWP 39) "ping_distribute" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
3 Thread 0x7f9fc11ba000 (LWP 40) "ping_distribute" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
4 Thread 0x7f9fbd1b9000 (LWP 41) "ping_distribute" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
5 Thread 0x7f9fabfff000 (LWP 42) "cuda00001400006" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
6 Thread 0x7f9f99fff000 (LWP 43) "async" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
7 Thread 0x7f9f95ffe000 (LWP 44) "ping_distribute" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
8 Thread 0x7f9f77fff000 (LWP 45) "dispatcher" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
9 Thread 0x7f9f73ffe000 (LWP 46) "async" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
10 Thread 0x7f9f6fffd000 (LWP 47) "worker" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
11 Thread 0x7f9f5bfff000 (LWP 48) "ping_distribute" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
12 Thread 0x7f9f57ffe000 (LWP 49) "dispatcher" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
13 Thread 0x7f9f53ffd000 (LWP 50) "async" 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
14 Thread 0x7f9f4fffc000 (LWP 51) "worker" 0x00007f9fc80e642f in __GI___wait4 (pid=pid@entry=52, stat_loc=stat_loc@entry=0x7f9f4fff6cfc, options=options@entry=0, usage=usage@entry=0x0) at ../sysdeps/unix/sysv/linux/wait4.c:30
很明显,线程 ID 14 负责段错误 (LWP 51
)。要进一步调查,我们可以在 GDB 中使用命令 thread 14
切换到此线程
(gdb) thread 14
切换后,我们可以使用 bt
命令检查此线程的回溯。
(gdb) bt
#0 0x00007f9fc80e642f in __GI___wait4 (pid=pid@entry=52, stat_loc=stat_loc@entry=0x7f9f4fff6cfc, options=options@entry=0, usage=usage@entry=0x0) at ../sysdeps/unix/sysv/linux/wait4.c:30
#1 0x00007f9fc80e63ab in __GI___waitpid (pid=pid@entry=52, stat_loc=stat_loc@entry=0x7f9f4fff6cfc, options=options@entry=0) at ./posix/waitpid.c:38
#2 0x00007f9fc6d72587 in ucs_debugger_attach () at /opt/ucx/src/contrib/../src/ucs/debug/debug.c:816
#3 0x00007f9fc6d7531d in ucs_error_freeze (message=0x7f9fc6d93c53 "address not mapped to object") at /opt/ucx/src/contrib/../src/ucs/debug/debug.c:919
#4 ucs_handle_error (message=0x7f9fc6d93c53 "address not mapped to object") at /opt/ucx/src/contrib/../src/ucs/debug/debug.c:1089
#5 ucs_handle_error (message=0x7f9fc6d93c53 "address not mapped to object") at /opt/ucx/src/contrib/../src/ucs/debug/debug.c:1077
#6 0x00007f9fc6d7545f in ucs_debug_handle_error_signal (signo=signo@entry=11, cause=0x7f9fc6d93c53 "address not mapped to object", fmt=fmt@entry=0x7f9fc6d93cf5 " at address %p") at /opt/ucx/src/contrib/../src/ucs/debug/debug.c:1038
#7 0x00007f9fc6d75746 in ucs_error_signal_handler (signo=11, info=0x7f9f4fff73b0, context=<optimized out>) at /opt/ucx/src/contrib/../src/ucs/debug/debug.c:1060
#8 <signal handler called>
#9 holoscan::ops::PingTensorTxOp::compute (this=0x5643fdcbd540, op_output=..., context=...) at /workspace/holoscan-sdk/src/operators/ping_tensor_tx/ping_tensor_tx.cpp:139
#10 0x00007f9fc9e415eb in holoscan::gxf::GXFWrapper::tick (this=0x5643fdcfef00) at /workspace/holoscan-sdk/src/core/gxf/gxf_wrapper.cpp:78
#11 0x00007f9fc88f0347 in nvidia::gxf::EntityExecutor::EntityItem::tickCodelet(nvidia::gxf::Handle<nvidia::gxf::Codelet> const&) () from /workspace/holoscan-sdk/build-x86_64/lib/libgxf_sample.so
#12 0x00007f9fc88f29c0 in nvidia::gxf::EntityExecutor::EntityItem::tick(long, nvidia::gxf::Router*) () from /workspace/holoscan-sdk/build-x86_64/lib/libgxf_sample.so
#13 0x00007f9fc88f31ee in nvidia::gxf::EntityExecutor::EntityItem::execute(long, nvidia::gxf::Router*, long&) () from /workspace/holoscan-sdk/build-x86_64/lib/libgxf_sample.so
#14 0x00007f9fc88f39d7 in nvidia::gxf::EntityExecutor::executeEntity(long, long) () from /workspace/holoscan-sdk/build-x86_64/lib/libgxf_sample.so
#15 0x00007f9fc8605dd9 in nvidia::gxf::MultiThreadScheduler::workerThreadEntrance(nvidia::gxf::ThreadPool*, long) () from /workspace/holoscan-sdk/build-x86_64/lib/libgxf_serialization.so
#16 0x00007f9fc8321253 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#17 0x00007f9fc8090ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#18 0x00007f9fc8121a04 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:100
在线程 14 的回溯下,您将找到
#8 <signal handler called>
#9 holoscan::ops::PingTensorTxOp::compute (this=0x5643fdcbd540, op_output=..., context=...) at /workspace/holoscan-sdk/src/operators/ping_tensor_tx/ping_tensor_tx.cpp:139
这表明段错误发生在 /workspace/holoscan-sdk/src/operators/ping_tensor_tx/ping_tensor_tx.cpp
的第 139 行。
要查看所有线程的回溯,请使用 thread apply all bt
命令。
(gdb) thread apply all bt
...
Thread 14 (Thread 0x7f9f4fffc000 (LWP 51) "worker"):
#0 0x00007f9fc80e642f in __GI___wait4 (pid=pid@entry=52, stat_loc=stat_loc@entry=0x7f9f4fff6cfc, options=options@entry=0, usage=usage@entry=0x0) at ../sysdeps/unix/sysv/linux/wait4.c:30
...
Thread 13 (Thread 0x7f9f53ffd000 (LWP 50) "async"):
#0 0x00007f9fc80e6612 in __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:29
...
Holoscan SDK 提供对跟踪和性能分析工具的支持,尤其侧重于 Python 运算符的 compute
方法。使用 Python IDE 调试 Python 运算符可能具有挑战性,因为此方法是从 C++ 运行时调用的。这也适用于 Python 运算符的 initialize
、start
和 stop
方法。
用户可以利用 VSCode/PyCharm 等 IDE(它们使用 PyDev.Debugger)或其他类似工具来调试 Python 运算符
对于 VSCode,请参阅 VSCode Python 调试。
对于 PyCharm,请查阅 PyCharm Python 调试。
后续章节将详细介绍使用 Holoscan SDK 调试、性能分析和跟踪 Python 应用程序的方法。
pdb 示例
以下命令在 pdb
会话中启动 Python 应用程序
python python/tests/system/test_pytracing.py pdb
# Type the following commands to check if the breakpoints are hit:
#
# b test_pytracing.py:78
# c
# exit
This is an interactive session.
Please type the following commands to check if the breakpoints are hit.
(Pdb) b test_pytracing.py:78
Breakpoint 1 at /workspace/holoscan-sdk/python/tests/system/test_pytracing.py:78
(Pdb) c
...
> /workspace/holoscan-sdk/python/tests/system/test_pytracing.py(78)start()
-> print("Mx start")
(Pdb) exit
有关更多详细信息,请参阅 test_pytracing.py
中的 pdb_main()
方法。
也可以启动 pdb
会话,而无需像在 test_pytracing.py 中那样在 main() 之前的源文件中手动添加 breakpoint()。在这种情况下,只需使用 python -m pdb my_app.py
启动现有应用程序即可。例如,我们可以像这样启动 SDK 附带的现有 ping_multi_port.py 示例
python -m pdb ./examples/ping_multi_port/python/ping_multi_port.py
然后我们将进入 pdb
提示符,从中我们可以插入断点(在本例中是 compute
方法的第一行),然后使用 c
继续执行,直到到达断点。
(Pdb) b ping_multi_port.py:97
Breakpoint 1 at /workspace/holoscan-sdk/build-x86_64/examples/ping_multi_port/python/ping_multi_port.py:97
(Pdb) c
[info] [fragment.cpp:588] Loading extensions from configs...
[info] [gxf_executor.cpp:262] Creating context
[info] [gxf_executor.cpp:1767] creating input IOSpec named 'in2'
[info] [gxf_executor.cpp:1767] creating input IOSpec named 'in1'
[info] [gxf_executor.cpp:1767] creating input IOSpec named 'receivers:1'
[info] [gxf_executor.cpp:1767] creating input IOSpec named 'receivers:0'
[info] [gxf_executor.cpp:1767] creating input IOSpec named 'receivers'
[info] [gxf_executor.cpp:2174] Activating Graph...
[info] [gxf_executor.cpp:2204] Running Graph...
[info] [gxf_executor.cpp:2206] Waiting for completion...
[info] [greedy_scheduler.cpp:191] Scheduling 3 entities
> /workspace/holoscan-sdk/build-x86_64/examples/ping_multi_port/python/ping_multi_port.py(97)compute()
-> value1 = op_input.receive("in1")
现在我们已经到达了所需的断点,我们可以交互式地调试运算符。以下示例显示了使用 s
单步执行一行,然后使用 p value1
打印 “value1” 变量的值。 l
命令用于显示周围的上下文。在输出中,箭头指示我们当前在调试器中所在的行,“B” 指示先前添加的断点。
(Pdb) s
> /workspace/holoscan-sdk/build-x86_64/examples/ping_multi_port/python/ping_multi_port.py(98)compute()
-> value2 = op_input.receive("in2")
(Pdb) p value1
ValueData(1)
(Pdb) l
93 spec.output("out2")
94 spec.param("multiplier", 2)
95
96 def compute(self, op_input, op_output, context):
97 B value1 = op_input.receive("in1")
98 -> value2 = op_input.receive("in2")
99 print(f"Middle message received (count: {self.count})")
100 self.count += 1
101
102 print(f"Middle message value1: {value1.data}")
103 print(f"Middle message value2: {value2.data}")
性能分析 Holoscan Python 应用程序
对于性能分析,用户可以使用 cProfile 或 line_profiler 等工具来分析 Python 应用程序/运算符的性能。
请注意,当使用多线程调度程序时,cProfile 或 profile 模块可能无法准确识别工作线程,或者可能会发生错误。
在具有多线程调度程序的情况下,请考虑使用多线程感知的性能分析器,如 pyinstrument、pprofile 或 yappi。
有关更多信息,请参阅 test_pytracing.py 中的测试用例。
使用 pyinstrument
pyinstrument 是 Python 的调用堆栈性能分析器,旨在以易于理解的格式直接在终端中突出显示代码执行时的性能瓶颈。
python -m pip install pyinstrument
pyinstrument python/tests/system/test_pytracing.py
## Note: With a multithreaded scheduler, the same method may appear multiple times across different threads.
# pyinstrument python/tests/system/test_pytracing.py -s multithread
...
0.107 [root] None
├─ 0.088 MainThread <thread>:140079743820224
│ └─ 0.088 <module> ../../../bin/pyinstrument:1
│ └─ 0.088 main pyinstrument/__main__.py:28
│ [7 frames hidden] pyinstrument, <string>, runpy, <built...
│ 0.087 _run_code runpy.py:63
│ └─ 0.087 <module> test_pytracing.py:1
│ ├─ 0.061 main test_pytracing.py:153
│ │ ├─ 0.057 MyPingApp.compose test_pytracing.py:141
│ │ │ ├─ 0.041 PingMxOp.__init__ test_pytracing.py:59
│ │ │ │ └─ 0.041 PingMxOp.__init__ ../core/__init__.py:262
│ │ │ │ [35 frames hidden] .., numpy, re, sre_compile, sre_parse...
│ │ │ └─ 0.015 [self] test_pytracing.py
│ │ └─ 0.002 [self] test_pytracing.py
│ ├─ 0.024 <module> ../__init__.py:1
│ │ [5 frames hidden] .., <built-in>
│ └─ 0.001 <module> ../conditions/__init__.py:1
│ [2 frames hidden] .., <built-in>
└─ 0.019 Dummy-1 <thread>:140078275749440
└─ 0.019 <module> ../../../bin/pyinstrument:1
└─ 0.019 main pyinstrument/__main__.py:28
[5 frames hidden] pyinstrument, <string>, runpy
0.019 _run_code runpy.py:63
└─ 0.019 <module> test_pytracing.py:1
└─ 0.019 main test_pytracing.py:153
├─ 0.014 [self] test_pytracing.py
└─ 0.004 PingRxOp.compute test_pytracing.py:118
└─ 0.004 print <built-in>
使用 pprofile
pprofile 是一个行粒度、线程感知的确定性和统计纯 Python 性能分析器。
python -m pip install pprofile
pprofile --include test_pytracing.py python/tests/system/test_pytracing.py -s multithread
Total duration: 0.972872s
File: python/tests/system/test_pytracing.py
File duration: 0.542628s (55.78%)
Line #| Hits| Time| Time per hit| %|Source code
------+----------+-------------+-------------+-------+-----------
...
33| 0| 0| 0| 0.00%|
34| 2| 2.86102e-06| 1.43051e-06| 0.00%| def setup(self, spec: OperatorSpec):
35| 1| 1.62125e-05| 1.62125e-05| 0.00%| spec.output("out")
36| 0| 0| 0| 0.00%|
37| 2| 3.33786e-06| 1.66893e-06| 0.00%| def initialize(self):
38| 1| 1.07288e-05| 1.07288e-05| 0.00%| print("Tx initialize")
39| 0| 0| 0| 0.00%|
40| 2| 1.40667e-05| 7.03335e-06| 0.00%| def start(self):
41| 1| 1.23978e-05| 1.23978e-05| 0.00%| print("Tx start")
42| 0| 0| 0| 0.00%|
43| 2| 3.09944e-05| 1.54972e-05| 0.00%| def stop(self):
44| 1| 2.88486e-05| 2.88486e-05| 0.00%| print("Tx stop")
45| 0| 0| 0| 0.00%|
46| 4| 4.05312e-05| 1.01328e-05| 0.00%| def compute(self, op_input, op_output, context):
47| 3| 2.57492e-05| 8.58307e-06| 0.00%| value = self.index
48| 3| 2.12193e-05| 7.07308e-06| 0.00%| self.index += 1
使用 yappi
yappi 是一个跟踪性能分析器,它具有多线程、asyncio 和 gevent 感知能力。
python -m pip install yappi
# yappi requires setting a context ID callback function to specify the correct context ID for
# Holoscan's worker threads.
# For more details, please see `yappi_main()` in `test_pytracing.py`.
python python/tests/system/test_pytracing.py yappi | grep test_pytracing.py
## Note: With a multithreaded scheduler, method hit counts are distributed across multiple threads.
#python python/tests/system/test_pytracing.py yappi -s multithread | grep test_pytracing.py
...
test_pytracing.py main:153 1
test_pytracing.py MyPingApp.compose:141 1
test_pytracing.py PingMxOp.__init__:59 1
test_pytracing.py PingTxOp.__init__:29 1
test_pytracing.py PingMxOp.setup:65 1
test_pytracing.py PingRxOp.__init__:99 1
test_pytracing.py PingRxOp.setup:104 1
test_pytracing.py PingTxOp.setup:34 1
test_pytracing.py PingTxOp.initialize:37 1
test_pytracing.py PingRxOp.stop:115 1
test_pytracing.py PingRxOp.initialize:109 1
test_pytracing.py PingMxOp.initialize:72 1
test_pytracing.py PingMxOp.stop:78 1
test_pytracing.py PingMxOp.compute:81 3
test_pytracing.py PingTxOp.compute:46 3
test_pytracing.py PingRxOp.compute:118 3
test_pytracing.py PingTxOp.start:40 1
test_pytracing.py PingMxOp.start:75 1
test_pytracing.py PingRxOp.start:112 1
test_pytracing.py PingTxOp.stop:43 1
使用 profile/ cProfile
profile/cProfile 是 Python 程序的确定性性能分析模块。
python -m cProfile python/tests/system/test_pytracing.py 2>&1 | grep test_pytracing.py
## Executing a single test case
#python python/tests/system/test_pytracing.py profile
1 0.001 0.001 0.107 0.107 test_pytracing.py:1(<module>)
1 0.000 0.000 0.000 0.000 test_pytracing.py:104(setup)
1 0.000 0.000 0.000 0.000 test_pytracing.py:109(initialize)
1 0.000 0.000 0.000 0.000 test_pytracing.py:112(start)
1 0.000 0.000 0.000 0.000 test_pytracing.py:115(stop)
3 0.000 0.000 0.000 0.000 test_pytracing.py:118(compute)
1 0.000 0.000 0.000 0.000 test_pytracing.py:140(MyPingApp)
1 0.014 0.014 0.073 0.073 test_pytracing.py:141(compose)
1 0.009 0.009 0.083 0.083 test_pytracing.py:153(main)
1 0.000 0.000 0.000 0.000 test_pytracing.py:28(PingTxOp)
1 0.000 0.000 0.000 0.000 test_pytracing.py:29(__init__)
1 0.000 0.000 0.000 0.000 test_pytracing.py:34(setup)
1 0.000 0.000 0.000 0.000 test_pytracing.py:37(initialize)
1 0.000 0.000 0.000 0.000 test_pytracing.py:40(start)
1 0.000 0.000 0.000 0.000 test_pytracing.py:43(stop)
3 0.000 0.000 0.000 0.000 test_pytracing.py:46(compute)
1 0.000 0.000 0.000 0.000 test_pytracing.py:58(PingMxOp)
1 0.000 0.000 0.058 0.058 test_pytracing.py:59(__init__)
1 0.000 0.000 0.000 0.000 test_pytracing.py:65(setup)
1 0.000 0.000 0.000 0.000 test_pytracing.py:72(initialize)
1 0.000 0.000 0.000 0.000 test_pytracing.py:75(start)
1 0.000 0.000 0.000 0.000 test_pytracing.py:78(stop)
3 0.001 0.000 0.001 0.000 test_pytracing.py:81(compute)
1 0.000 0.000 0.000 0.000 test_pytracing.py:98(PingRxOp)
1 0.000 0.000 0.000 0.000 test_pytracing.py:99(__init__)
使用 line_profiler
line_profiler 是一个用于对函数进行逐行性能分析的模块。
python -m pip install line_profiler
# Insert `@profile` before the function `def compute(self, op_input, op_output, context):`.
# The original file will be backed up as `test_pytracing.py.bak`.
file="python/tests/system/test_pytracing.py"
pattern=" def compute(self, op_input, op_output, context):"
insertion=" @profile"
if ! grep -q "^$insertion" "$file"; then
sed -i.bak "/^$pattern/i\\
$insertion" "$file"
fi
kernprof -lv python/tests/system/test_pytracing.py
# Remove the inserted `@profile` decorator.
mv "$file.bak" "$file"
...
Wrote profile results to test_pytracing.py.lprof
Timer unit: 1e-06 s
Total time: 0.000304244 s
File: python/tests/system/test_pytracing.py
Function: compute at line 46
Line # Hits Time Per Hit % Time Line Contents
==============================================================
46 @profile
47 def compute(self, op_input, op_output, context):
48 3 2.3 0.8 0.8 value = self.index
49 3 9.3 3.1 3.0 self.index += 1
50
51 3 0.5 0.2 0.2 output = []
52 18 5.0 0.3 1.6 for i in range(0, 5):
53 15 4.2 0.3 1.4 output.append(value)
54 15 2.4 0.2 0.8 value += 1
55
56 3 280.6 93.5 92.2 op_output.emit(output, "out")
...
测量代码覆盖率
Holoscan SDK 提供使用 Coverage.py 测量代码覆盖率的支持。
python -m pip install coverage
coverage erase
coverage run examples/ping_vector/python/ping_vector.py
coverage report examples/ping_vector/python/ping_vector.py
coverage html
# Open the generated HTML report in a browser.
xdg-open htmlcov/index.html
要以编程方式记录代码覆盖率,请参阅 test_pytracing.py
中的 coverage_main()
方法。
您可以通过运行以下命令来执行启用代码覆盖率的示例应用程序
python -m pip install coverage
python python/tests/system/test_pytracing.py coverage
# python python/tests/system/test_pytracing.py coverage -s multithread
以下命令使用 trace
启动 Python 应用程序
python -m trace --trackcalls python/tests/system/test_pytracing.py | grep test_pytracing
...
test_pytracing.main -> test_pytracing.MyPingApp.compose
test_pytracing.main -> test_pytracing.PingMxOp.compute
test_pytracing.main -> test_pytracing.PingMxOp.initialize
test_pytracing.main -> test_pytracing.PingMxOp.start
test_pytracing.main -> test_pytracing.PingMxOp.stop
test_pytracing.main -> test_pytracing.PingRxOp.compute
test_pytracing.main -> test_pytracing.PingRxOp.initialize
test_pytracing.main -> test_pytracing.PingRxOp.start
test_pytracing.main -> test_pytracing.PingRxOp.stop
test_pytracing.main -> test_pytracing.PingTxOp.compute
test_pytracing.main -> test_pytracing.PingTxOp.initialize
test_pytracing.main -> test_pytracing.PingTxOp.start
test_pytracing.main -> test_pytracing.PingTxOp.stop
可以在 test_pytracing.py
中的 trace_main()
方法中找到以编程方式使用 trace
模块的测试用例。
python python/tests/system/test_pytracing.py trace
# python python/tests/system/test_pytracing.py trace -s multithread