本节将介绍如果通过 debug 的方式调试代码,除了使用打印调试、日志、调试器以外,还将会介绍如果对代码进行性能分析。

目录如下:

对于打印调试来说,这是最简单、最直接的方法,也就是通过输出语句,直接输出一些变量、返回值的信息,通过这些输出的信息来判断程序中的代码是否执行出错。当然,也可以将这些信息一起写入到日志、socket 或者远程服务器中。

除了直接输出变量信息以外,还可以对日志设置严重等级。例如INFODEBUGWARNERROR等,以便于根据需要进行过滤。

然而,在有些情况下,仅通过客户端的代码错误信息可能并不足以定位问题。例如一些大型的软件系统,它们可能会依赖一些其它的应用程序。这些应用程序都会将日志保存在系统中的某个位置。例如对于 UNIX 来说,程序的日志通常保存在/var/log下。你可以通过一些工具来更好地展示和浏览日志文件。

以上的方法是通过日志信息来追踪错误,还可以通过调试器来一步一步的找到具体的错误代码。例如常见的编辑器IntelliJ IDEA中就带有调试器(Python 的终端调试器是 pdb)。你可以在可能会出现代码错误的地方打一个断点,然后通过不同的命令来让程序运行到某一行或一次执行一条语句的方式,直至定位问题。

如果你调试的是那种类似于二进制的程序,那么可以使用专门的工具进行调试。当你的程序中需要执行一些只有操作系统内核才能完成的操作时,则需要经过系统调用。在 Linux 下可以通过 strace 命令来追踪程序执行的系统调用。如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
carol@ubuntu-carol:~$ sudo strace ls -l > /dev/null 
execve("/usr/bin/ls", ["ls", "-l"], 0x7ffdef66c4f8 /* 16 vars */) = 0
brk(NULL)                               = 0x55899474c000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd296c75b0) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=65827, ...}) = 0
mmap(NULL, 65827, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fbcccaa9000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@p\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=163200, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbcccaa7000
mmap(NULL, 174600, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fbccca7c000
mprotect(0x7fbccca82000, 135168, PROT_NONE) = 0
mmap(0x7fbccca82000, 102400, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x7fbccca82000
mmap(0x7fbccca9b000, 28672, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1f000) = 0x7fbccca9b000
mmap(0x7fbcccaa3000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x26000) = 0x7fbcccaa3000
mmap(0x7fbcccaa5000, 6664, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fbcccaa5000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
...
(more information)
...
carol@ubuntu-carol:~$ 

strace是如何追踪系统调用的?情况这篇文章 Strace – The Sysadmin’s Microscope

此外,如果你是 Web 开发者或是前端开发,那么可以使用 ChromeFirefox 开发者工具进行相应的调试。

在有些情况下,并不需要执行代码就可以发现问题所在。例如,某个变量在被读取之前没有被定义过。这时可以使用静态分析工具这道这个问题。静态分析的原理是将程序代码作为输入,然后基于编码规则对其进行分析并对代码的正确性进行推理。针对 Python 语言可以使用 pyflakesmypy 工具。在 Vim 下,可以使用插件 alesyntastic。而对于其它语言来说,你可以在 Github 上的 static analysis 找到对应语言的分析工具。

而对于代码风格检查和格式化工具,Python 语言可以使用 black,Go 语言可以使用 gofmt,JavaScript、HTML 和 CSS 可以使用 Prettier

对于那些系统中最耗时、最耗资源的代码,也可以有针对性的进行分析。此时就需要用到性能分析工具。针对 CPU 性能分析的工具有两种:追踪分析器和采样分析器。前者会记录程序的每一次函数调用,而后者会周期性的监测程序并记录程序堆栈。对于 Python 可以使用 cProfile 来进行性能分析。如果你是 C/C++ 的使用者,那么有可能会产生内存泄漏的问题,导致程序在使用完内存后而没有释放。此时可以使用 Valgrind 来帮助你分析内存。

你可能会觉得上面的基于命令行的方式不能很好地展示一些信息,为了方便信息的展示,我们可以使用火焰图进行可视化操作。火焰图的横轴表示 CPU 耗时的比例,纵轴表示函数调用关系。你甚至可以与火焰图进行交互,深入程序的某一个具体部分查看栈信息并进行追踪。

除了程序性能分析之外,对系统资源的监控也尤其重要。以下列出了许多工具来显示不同的系统资源(例如,CPU 占用、内存使用、网络状况、磁盘使用情况等)。

  • tophtop: 可以显示当前运行进程的多种统计信息。
  • iotop: 实时显示 I/O 占用信息。
  • df: 显示磁盘分区信息。
  • free: 显示系统当前空闲内存。
  • lsof: 显示被进程打开的文件信息。
  • ss: 用于监控网络包的收发情况以及网络接口的显示信息。
  • nethogsiftop: 用于对网络占用进行监控的交互式命令行工具。

以上。