Linux性能学习第七周-综合实战

5689 words, 17 mins

linux 性能优化学习第七周总结,本周是综合实战

容器化应用

容器本身可以通过cggroups进行资源隔离,比如可以限制容器 CPU 和 MEM 的最大使用占比

比如下面的命令:

  • --cpus 0.1 :只能使用 10%的CPU
  • -m 512M: 只能使用 512M 的内存
$ docker run --name tomcat --cpus 0.1 -m 512M -p 8080:8080 -itd feisky/tomcat:8

而一旦限制了资源,一些应用在容器中资源耗尽,就会挂掉或者工作缓慢,这时可以先查看docker 的日志:

$ docker logs -f tomcat

再查看 docker 中进程的状态

$ docker ps -a
# 或查看指定应用状态的 json 格式的输出
$ docker inspect tomcat -f '' | jq

如果进程挂掉了,可以通过dmesg命令查看系统日志

# -H 输出格式更容易让人理解,比如时间戳会转换成可读格式
$ dmesg -H

可以不直接进入容器运行指令检查应用

# 查看堆内存,注意单位是字节
$ docker exec tomcat java -XX:+PrintFlagsFinal -version | grep HeapSize
# 查看内存
$ docker exec tomcat free -m

可以直接进入容器的 bash 终端,再运行别的指令检查应用

$ docker exec -it tomcat bash

也可以在启动容器时指定初始命令

$ docker run --name tomcat --cpus 0.1 -m 512M -e JAVA_OPTS='-Xmx512m -Xms512m' -p 8080:8080 -itd feisky/tomcat:8

同时使用top指令观察CPU 使用率

$ top

当发现异常进程后,再使用pidstat分析指定进程

# -t表示显示线程,-p指定进程号
$ pidstat -t -p 29457 1

丢包问题检查

丢包可能的地方很多,需要通过网络收发流程图来逐步排查

img

  • 在两台 VM 连接之间,可能会发生传输失败的错误,比如网络拥塞、线路错误等;
  • 在网卡收包后,环形缓冲区可能会因为溢出而丢包;
  • 在链路层,可能会因为网络帧校验失败、QoS 等而丢包;可使用netstat -i查看丢包统计
  • 在 IP 层,可能会因为路由失败、组包大小超过 MTU 等而丢包; 可使用netstat -i查看mtu
  • 在传输层,可能会因为端口未监听、资源占用超过内核限制等而丢包;可使用netstat -s查看协议统计
  • 在套接字层,可能会因为套接字缓冲区溢出而丢包;
  • 在应用层,可能会因为应用程序异常而丢包; 可使用tcpdump -i eth0 -nn port 80查看指定端口网络包
  • 此外,如果配置了 iptables 规则,这些网络包也可能因为 iptables 过滤规则而丢包。可使用iptables -t filter -nvL查看过滤规则

内核线程CPU 利用率高

  • 使用 top命令查看占用 CPU 的进程 id
  • 使用perf record采样,$ perf record -a -g -p 9 -- sleep 30,然后执行perf report展开调用堆栈查看

还可以使用火焰图,更直观的展示

img

  • 横轴表示采样数和采样比例。横轴越宽,代表执行时间越长
  • 纵轴表示调用栈。由下往上根据调用关系逐个展开。纵轴越高,代表调用栈越深
  • 颜色深浅没有特殊含义,只为函数间的区分

上图表示 CPU 的繁忙情况,还可以查看内存的分配和释放情况。

生成火焰图的方法:

  • 安装FlameGraph, git clone https://github.com/brendangregg/FlameGraph
  • 一行代码生成火焰图:$ perf script -i /root/perf.data | ./stackcollapse-perf.pl --all | ./flamegraph.pl > ksoftirqd.svg

动态追踪

通过探针机制,来采集内核或者应用程序的运行信息,从而可以不用修改内核和应用程序的代码,就获得丰富的信息,帮你分析、定位想要排查的问题。

真实的线上项目,不能下断点,也不能停机进行分析,使用动态追踪技术可以在不停机,不修改代码的情况下直接进行分析,并且性能损耗相对于ptrace这种进程级跟踪方法会小很多(通常在 5% 或者更少)。

ftrace

sudo apt-get install trace-cmd

ls命令为例

sudo trace-cmd record -p function_graph -g do_sys_open -O funcgraph-proc ls
sudo trace-cmd report

perf

安装调试符号信息, 参考https://wiki.ubuntu.com/Debug%20Symbol%20Packages

codename=$(lsb_release -c | awk  '{print $2}')
sudo tee /etc/apt/sources.list.d/ddebs.list << EOF
deb http://ddebs.ubuntu.com/ ${codename}      main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-security main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-updates  main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-proposed main restricted universe multiverse
EOF
sudo apt install ubuntu-dbgsym-keyring
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F2EDC64DC5AEE1F6B9C621F0C8CAB6595FDFF622
sudo apt-get update
sudo apt-get install linux-image-$(uname -r)-dbgsym

以观察内核函数 do_sys_open 为例子

  • 查看函数的定义,参数的类型
$ sudo perf probe -V do_sys_open
Available variables at do_sys_open
        @<do_sys_open+0>
                char*   filename
                int     dfd
                int     flags
                struct open_flags       op
                umode_t mode

这里我们比较关注的是filename,也就是希望看到打开的文件名称,如果查看不到以上内容,说明调试符号没有安装好。

  • 添加探针
sudo perf probe --add 'do_sys_open filename:string'

注意,如果只写函数名称,是看不到函数打开的文件名的

  • 采样
sudo perf record -e probe:do_sys_open -aR ls
  • 查看结果
$ sudo perf script
            perf 23321 [002] 1216304.406239: probe:do_sys_open: (ffffffff93276e90) filename_string="/proc/23324/status"
              ls 23324 [001] 1216304.406588: probe:do_sys_open: (ffffffff93276e90) filename_string="/etc/ld.so.cache"
              ls 23324 [001] 1216304.406603: probe:do_sys_open: (ffffffff93276e90) filename_string="/lib/x86_64-linux-gnu/libselinux.so.1"
...
              ls 23324 [001] 1216304.407014: probe:do_sys_open: (ffffffff93276e90) filename_string="/usr/lib/locale/zh.UTF-8/LC_CTYPE"
              ls 23324 [001] 1216304.407017: probe:do_sys_open: (ffffffff93276e90) filename_string="/usr/lib/locale/zh.utf8/LC_CTYPE"
              ls 23324 [001] 1216304.407021: probe:do_sys_open: (ffffffff93276e90) filename_string="/usr/lib/locale/zh/LC_CTYPE"
              ls 23324 [001] 1216304.407034: probe:do_sys_open: (ffffffff93276e90) filename_string="."
  • 删除探针
sudo perf probe --del probe:do_sys_open

eBPF 和 BCC

eBPF 就是 Linux 版的 DTrace,可以通过 C 语言自由扩展

BCC(BPF Compiler Collection,把这些过程通过 Python 抽象起来,使用 BCC 进行动态追踪时,编写简单的脚本就可以了。

  • 安装 BCC

# Ubuntu
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
echo "deb https://repo.iovisor.org/apt/$(lsb_release -cs) $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/iovisor.list
sudo apt-get update
sudo apt-get install bcc-tools libbcc-examples linux-headers-$(uname -r)

  • 使用 python 脚本监控do_sys_open
from bcc import BPF
# define BPF program (""" is used for multi-line string).
# '#' indicates comments for python, while '//' indicates comments for C.
prog = """
#include <uapi/linux/ptrace.h>
#include <uapi/linux/limits.h>
#include <linux/sched.h>
// define output data structure in C
struct data_t {
    u32 pid;
    u64 ts;
    char comm[TASK_COMM_LEN];
    char fname[NAME_MAX];
};
BPF_PERF_OUTPUT(events);

// define the handler for do_sys_open.
// ctx is required, while other params depends on traced function.
int hello(struct pt_regs *ctx, int dfd, const char __user *filename, int flags){
    struct data_t data = {};
    data.pid = bpf_get_current_pid_tgid();
    data.ts = bpf_ktime_get_ns();
    if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) {
        bpf_probe_read(&data.fname, sizeof(data.fname), (void *)filename);
    }
    events.perf_submit(ctx, &data, sizeof(data));
    return 0;
}
"""
# load BPF program
b = BPF(text=prog)
# attach the kprobe for do_sys_open, and set handler to hello
b.attach_kprobe(event="do_sys_open", fn_name="hello")

# process event
start = 0
def print_event(cpu, data, size):
    global start
    event = b["events"].event(data)
    if start == 0:
            start = event.ts
    time_s = (float(event.ts - start)) / 1000000000
    print("%-18.9f %-16s %-6d %-16s" % (time_s, event.comm, event.pid, event.fname))

# loop with callback to print_event
b["events"].open_perf_buffer(print_event)


# print header
print("%-18s %-16s %-6s %-16s" % ("TIME(s)", "COMM", "PID", "FILE"))
# start the event polling loop
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()
  • 开始监控
sudo python trace-open.py

SystemTap 和 sysdig

从稳定性上来说,SystemTap 只在 RHEL 系统中好用,在其他系统中则容易出现各种异常问题

sysdig 则是随着容器技术的普及而诞生的,主要用于容器的动态追踪。sysdig 汇集了一些列性能工具的优势,可以说是集百家之所长。 sysdig 的特点: sysdig = strace + tcpdump + htop + iftop + lsof + docker inspect。

几个常见的动态追踪使用场景

img

eBPF编写可以参考https://docs.cilium.io/en/stable/bpf/