The missing semester of your CS education--调试及性能分析

本文档介绍了计算机科学教育中经常被忽视的调试和性能分析环节,详细讲解了如何使用各种工具和技术进行代码调试,如日志、调试器、专门工具,以及性能分析的计时、CPU、内存等方面的考量。通过实例展示了如何利用Python的pdb、日志系统、性能分析工具如cProfile、line_profiler等进行代码优化。此外,还提到了系统日志监控、网络数据包分析、资源监控工具等,以及在Linux和macOS中进行性能分析的方法。文章旨在帮助读者掌握调试和性能分析的基本技能,提升软件开发效率和质量。
摘要由CSDN通过智能技术生成

课程结构

01.课程概览与 shell
02.Shell 工具和脚本
03.编辑器 (Vim)
04.数据整理
05.命令行环境
06.版本控制(Git)
07.调试及性能分析
08.元编程
09.安全和密码学
10.大杂烩
11.提问&回答

本文档修改自这里,补充了一些视频中展示但配套文档中未提供的代码,以及一些注释,另外,本节中涉及的相关文件可在百度云链接中获取。

调试代码

打印调试法与日志

“最有效的 debug 工具就是细致的分析,配合恰当位置的打印语句” — Brian Kernighan, Unix 新手入门

调试代码的第一种方法往往是在您发现问题的地方添加一些打印语句,然后不断重复此过程直到您获取了足够的信息并找到问题的根本原因。

另外一个方法是使用日志,而不是临时添加打印语句。日志较普通的打印语句有如下的一些优势:

  • 您可以将日志写入文件、socket 或者甚至是发送到远端服务器而不仅仅是标准输出;
  • 日志可以支持严重等级(例如 INFO, DEBUG, WARN, ERROR等),这使您可以根据需要过滤日志;
  • 对于新发现的问题,很可能您的日志中已经包含了可以帮助您定位问题的足够的信息。

logger.py 是一个包含日志的例程序:

$ python3 logger.py # 简单的输出
Value is 8 - Dangerous region   
...
$ python3 logger.py log # 带有格式化的输出
2023-05-05 09:15:52,716 : INFO : Sample : Value is 1 - Everything is fine
...
$ python3 logger.py log ERROR   # 格式化的输出ERROR层级以上的内容:ERROR&CRITICAL
2023-05-05 09:16:05,641 : CRITICAL : Sample : Maximum value reached
...
$ python3 logger.py color   # 带有颜色的格式化输出
2023-05-05 09:16:28,727 - Sample - CRITICAL - Maximum value reached (logger.py:64)  
...

有很多技巧可以使日志的可读性变得更好,例如着色。

lsgrep 这样的程序会使用 ANSI escape codes,它是一系列的特殊字符,可以使您的 shell 改变输出结果的颜色。

~ $ echo -e "\e[38;2;255;0;0mThis is red\e[0m"
This is red

 # 如果你的终端不支持真彩色,可以使用更广泛的 16色
~ $ echo -e "\e[31;1mThis is red\e[0m"
This is red

下面这个脚本向您展示了如何在终端中打印多种颜色(只要您的终端支持真彩色)

~ $ vim color.sh
~ $ cat color.sh

#!/usr/bin/env bash
for R in $(seq 0 20 255); do
    for G in $(seq 0 20 255); do
        for B in $(seq 0 20 255); do
            printf "\e[38;2;${R};${G};${B}m█\e[0m";
        done
    done
done

~ $ source color.sh

第三方日志系统

如果您正在构建大型软件系统,您很可能会使用到一些依赖,有些依赖会作为程序单独运行。如 Web 服务器、数据库或消息代理都是此类常见的第三方依赖。

和这些系统交互的时候,阅读它们的日志是非常必要的,因为仅靠客户端侧的错误信息可能并不足以定位问题。

幸运的是,大多数的程序都会将日志保存在您的系统中的某个地方。对于 UNIX 系统来说,程序的日志通常存放在 /var/log。例如, NGINX web 服务器就将其日志存放于/var/log/nginx

目前,系统开始使用 system log,您所有的日志都会保存在这里。大多数(但不是全部的)Linux 系统都会使用 systemd,这是一个系统守护进程,它会控制您系统中的很多东西,例如哪些服务应该启动并运行。systemd 会将日志以某种特殊格式存放于/var/log/journal,您可以使用 journalctl 命令显示这些消息。

类似地,在 macOS 系统中是 /var/log/system.log,但是有更多的工具会使用系统日志,它的内容可以使用 log show 显示。

对于大多数的 UNIX 系统,您也可以使用dmesg 命令来读取内核的日志。

如果您希望将日志加入到系统日志中,您可以使用 logger 这个 shell 程序。

logger "Hello Logs"

# On macOS
log show --last 1m | grep Hello
# On Linux
journalctl --since "1m ago" | grep Hello

视频中提到一个应用场景:你可以编写一个bash脚本,监控wifi,当wifi切换连上另一个网络时,向系统日志写入内容,类似"It’s XXX time now, we’ve changed our connection to XXX!",稍后,可以浏览系统日志,用以研究wifi的切换是否导致了某些系统问题。

日志的内容可以非常的多,需要对其进行处理和过滤才能得到想要的信息。

如果您发现您需要对 journalctllog show 的结果进行大量的过滤,那么此时可以考虑使用它们自带的选项对其结果先过滤一遍再输出。还有一些像 lnav 这样的工具,它为日志文件提供了更好的展现和浏览方式,如执行cat /var/log/syslog | lnav

调试器

当通过打印已经不能满足您的调试需求时,您应该使用调试器。

调试器是一种可以允许我们和正在执行的程序进行交互的程序,它可以做到:

  • 当到达某一行时将程序暂停;
  • 一次一条指令地逐步执行程序;
  • 程序崩溃后查看变量的值;
  • 满足特定条件时暂停程序;
  • 其他高级功能。

很多编程语言都有自己的调试器。Python 自带的调试器是pdb.

下面对pdb 支持的命令进行简单的介绍:

  • l(ist) - 显示当前行附近的11行或继续执行之前的显示;
  • s(tep) - 执行当前行,并在第一个可能的地方停止;
  • n(ext) - 继续执行直到当前函数的下一条语句或者 return 语句;
  • b(reak) - 设置断点(基于传入的参数);
  • p(rint) - 在当前上下文对表达式求值并打印结果。还有一个命令是pp ,它使用 pprint 打印;
  • r(eturn) - 继续执行直到当前函数返回;
  • q(uit) - 退出调试器。

让我们修复下面的 Python 代码(bubble.py

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(n):
            if arr[j] > arr[j+1]:
                arr[j] = arr[j+1]
                arr[j+1] = arr[j]
    return arr

print(bubble_sort([4, 2, 1, 8, 7, 6]))
  • 下面使用ipdb来调试代码:
    ~ $ sudo apt install pip
    ~ $ pip install ipdb
    ~ $ mkdir debug; cd debug
    ~/debug $ vim bubble.py   # 输入上面的python的代码
    ~/debug $ python3 bubble.py
     # 执行代码,报错:IndexError
    
    ~/debug $ python3 -m ipdb bubble.py
    /usr/lib/python3.10/runpy.py:126: RuntimeWarning: 'ipdb.__main__' found in sys.modules after import of package 'ipdb', but prior to execution of 'ipdb.__main__'; this may result in unpredictable behaviour
    warn(RuntimeWarning(msg))
    > /home/laihj/debug/bubble.py(1)<module>()
    ----> 1 def bubble_sort(arr):
        2     n = len(arr)
        3     for i in range(n):
    
  • 展示当前行(第一行)附近的11行
    ipdb> l 
    ----> 1 def bubble_sort(arr):
        2     n = len(arr)
        3     for i in range(n):
        4         for j in range(n):
        5             if arr[j] > arr[j+1]:
        6                 arr[j] = arr[j+1]
        7                 arr[j+1] = arr[j]
        8     return arr
        9
        10 print(bubble_sort([4, 2, 1, 8, 7, 6]))
    
  • 逐行执行代码,在第一个可能出错的地方停下
    ipdb> s
    > /home/laihj/debug/bubble.py(10)<module>()
        8     return arr
        9
    ---> 10 print(bubble_sort([4, 2, 1, 8, 7, 6]))
    
  • 使用s后,只要输入<Enter>键,就可继续逐行运行代码,直至出错的地方
    ipdb> 
    --Call--
    > /home/laihj/debug/bubble.py(1)bubble_sort()
    ----> 1 def bubble_sort(arr):
        2     n = len(arr)
        3     for i in range(n):
    
    ipdb>
    > /home/laihj/debug/bubble.py(2)bubble_sort()
        1 def bubble_sort(arr):
    ----> 2     n = len(arr)
        3     for i in range(n):
    
  • 使用 s 逐行运行,到达出错的地方,效率可能太慢,重新开始调试
    ipdb> restart
    Restarting bubble.py with arguments:
    
    > /home/laihj/debug/bubble.py(1)<module>()
    ----> 1 def bubble_sort(arr):
        2     n = len(arr)
        3     for i in range(n):
    
  • 使用 c ,直接运行至出错的地方
    ipdb> c
    Traceback (most recent call last):
    File "/home/laihj/.local/lib/python3.10/site-packages/ipdb/__main__.py", line 323, in main
        pdb._runscript(mainpyfile)
    File "/usr/lib/python3.10/pdb.py", line 1586, in _runscript
        self.run(statement)
    File "/usr/lib/python3.10/bdb.py", line 597, in run
        exec(cmd, globals, locals)
    File "<string>", line 1, in <module>
    File "/home/laihj/debug/bubble.py", line 10, in <module>
        print(bubble_sort([4, 2, 1, 8, 7, 6]))
    File "/home/laihj/debug/bubble.py", line 5, in bubble_sort
        if arr[j] > arr[j+1]:
    IndexError: list index out of range
    Uncaught exception. Entering post mortem debugging
    Running 'cont' or 'step' will restart the program
    > /home/laihj/debug/bubble.py(5)bubble_sort()
        4         for j in range(n):
    ----> 5             if arr[j] > arr[j+1]:
        6                 arr[j] = arr[j+1]
    
  • 此时,报出IndexError。使用 p 打印变量的当前值进行检查。
    ipdb> p arr
    [2, 1, 1, 7, 6, 6]
    ipdb> p j
    5
    
  • 此时,j+1=6,arr[6]不存在,需要修改for j in range(n):中的nn-1。退出调试,并直接修改bubble.py
    ipdb> q
    ipdb> q
    ~/debug $ vim bubble.py
    
  • 重新执行bubble.py,发现还有错误,再次进行调试。
    ~/debug $ python3 bubble.py
    [1, 1, 1, 6, 6, 6]
    ~/debug $ python3 -m ipdb bubble.py
    /usr/lib/python3.10/runpy.py:126: RuntimeWarning: 'ipdb.__main__' found in sys.modules after import of package 'ipdb', but prior to execution of 'ipdb.__main__'; this may result in unpredictable behaviour
    warn(RuntimeWarning(msg))
    > /home/laihj/debug/bubble.py(1)<module>()
    ----> 1 def bubble_sort(arr):
        2     n = len(arr)
        3     for i in range(n):
    
    ipdb> l
    ----> 1 def bubble_sort(arr):
        2     n = len(arr)
        3     for i in range(n):
        4         for j in range(n-1):
        5             if arr[j] > arr[j+1]:
        6                 arr[j] = arr[j+1]
        7                 arr[j+1] = arr[j]
        8     return arr
        9
        10 print(bubble_sort([4, 2, 1, 8, 7, 6]))
    
  • 我们现在要找出代码中哪里修改了arr中的值,从代码来看,只有if结构中的语句有可能。为此,在这里设置断点breakpoint
    ipdb> b 6
    Breakpoint 1 at /home/laihj/debug/bubble.py:6
    ipdb> c
    > /home/laihj/debug/bubble.py(6
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值