半年前翻了 Systems Performance(性能之巅)的一小部分,现在进一步阅读并做点复查用的笔记。

目前打算是分成两篇,一篇介绍基本理论,另一篇介绍工作流。本篇为工作流。

注意这里提到的是系统性能存在异常时的工作流,不涉及稳定性和常规性能优化。

保命声明:显然这是一个很有深度的话题,然而笔者的水平只能浅到不能再浅了……
免责声明:这是一篇未完工的文章!

用工具观测资源

tools 非常有名的工具大全

上一篇文章介绍用于性能分析的 USE 方法:从资源的利用率、饱和度和错误去看待性能。是否存在性能问题主要看饱和度和错误:若存在性能问题,必有过饱和或者错误;若未存在性能问题,可以通过排队论对利用率的临界点进行预测。再直白点说,如果在系统中想要获取某一项资源,但是获取的过程发生了停顿等待(排队)的行为,那就是存在性能问题。

观测资源则需要用到工具。但是如上图所示,工具(还有指标)的选择非常多,不同背景的开发者对资源的划分也有不同的见解,因此还是要准备一份适合自己的工作流。

NOTE: 上一篇文章也提到 PSI 是一个非常好用的系统压力指标,可以避免各种工具的学习成本,这里不再重复介绍了。

CPU

CPU 的主要性能指标有:平均负载(load average)、CPU 使用率(CPU usage,%CPU)和IPC(instructions per cycle)。

平均负载跟踪处于 TASK_RUNNING (R) 加上 TASK_UNIERRUPTED (D) 状态的任务数目,并且按照每 1 分钟、5 分钟和 15 分钟的粒度给出三个平滑处理过的数值。这些数据用于动态观察系统整体负载(计算负载和 IO 负载)的变化趋势。(这里负载指的是需要运行处理但是可能允许排队的任务。)有两个诊断用的经验方法:理想状态认为数值低于 CPU 数目;高于 70%CPU 数目则认为是高负载。该指标可以使用 uptimetop 或者 cat /proc/loadavg 命令获取。如果需要统计分离的 R 状态和 D 状态的任务数目,可以使用 dstat -p 命令获取。

系统级别的 CPU 使用率划分了不同类型的 CPU 时间占比,包含 usr、nice、sys、iowait、irq、soft 和 idle 等负载类型,按照百分比显示。on-CPU 相关需要关注前三项,如果是 usr 低占比的同时 sys 高占比,可能存在两种情况:一是用户层的代码足够好,只剩下高占比的系统调用;二是用户层的代码足够差,有大量使用不当的系统调用。该指标可以使用 topdstat -c 或者 cat /proc/stat | grep cpu(需解析)命令获取。每 CPU 级别可以直接使用 mpstat -P ALLsar -P ALL 1 或者 top 交互面板按 1 命令获取。

任务级别的 CPU 使用率计算非空闲状态的 CPU 时间(non-idle time)占比,其结果仅有一个百分比数值,这里的非空闲时间指的是系统时间(stime)加上用户时间(utime)。该指标可以使用 top 命令获取。

IPC 是每时钟周期所执行的平均指令数。有一种说法认为前面提到的非空闲时间不等价于繁忙时间,事实上还可能包含了停顿时间(iowait),也就是不应该使用 CPU 使用率来衡量 CPU 繁忙程度,而是使用 IPC 来衡量。(需要注意系统 CPU 使用率显示可以细化到多种分类,iowait 是可以单独展示的。而任务的 CPU 使用率只显示一个百分比,是否包含 iowait 还取决于工具的版本或算法,这里确实是个含糊的地方。)IPC 数值的简单划分方式是:IPC 小于 1.0 很可能是出现了停顿问题(off-CPU),而大于等于 1.0 则至少可认为是正常情况。如果需要更精准的划分,可以自行定制。该指标可以使用 perf stat(默认是 system wide 模式,即 -a)或者 tiptop 命令获取。

Here’s how you can get a value that’s custom for your system and runtime: write two dummy workloads, one that is CPU bound, and one memory bound. Measure their IPC, then calculate their mid point.

Memory

内存的主要性能指标有:内存利用率(%MEM)、页面回收效率(%vmeff)和任务的内存映射区域(smaps)。

内存利用率是容量意义上的利用率。系统级别的内存利用信息都集中在 meminfo,其可用内存份额为 MemAvailable(一个估算值),系统的利用率可以按照 (MemTotal - MemAvailable) / MemTotal 计算;任务级别的内存利用率则是按照 RSS 计算。这些内容的解析可以看这篇文章。该指标可以使用 topfree 或者 cat /proc/meminfo 命令获取。

页面回收效率跟踪 kswapd 页面回收和直接页面回收的压力情况。因为 Linux 系统本身善用页面缓存(page cache),所以内存利用信息里面统计的 MemFree 和 MemAvailable 存在一定数值差距。但是当内存使用存在压力时,可用内存份额是否完全释放出页面缓存作为救急用内存,这还需要观察 %vmeff (= pgsteal / (pgscank + pgscand)) 即页面回收效率。从 man page 可以了解到,理想状态为 100%,但是降低到一定程度如 30% 就可能是 memory bound。该指标可以使用 sar -B 命令获取。

任务内存映射区域可以按照进程的每个内存区域进行内存信息统计。最简单的分析方法就是查看数值是否符合程序实现的预期。该指标可以使用 cat /proc/[pid]/smaps 命令获取。

NOTE: 内存的压力有一部分来自缓存,尤其是程序自身实现的缓存,这部分的缓存让系统误判为是一时无法回收的资源。因此在内存利用率上,我们能看到的系统层面的内存统计信息不一定是事实正确的。比如 meminfo 提示可用内存份额已经小于 10MB,但是实际上有高达 10GB 的程序缓存可以直接释放,这是系统层面无法直接得知的信息。因此,这方面的分析我觉得需要程序自行实现缓存信息统计的工具或命令。

TODO: 关于 PSS 和 RSS 的选择。工具一般提供 RSS 作为默认选择,但是 Android 平台经常是以 PSS 的形式给出。我也不知道谷歌的想法……调研待办。

Disk

通常 IO 设备(包括硬盘)有三个主要性能指标:IOPS、吞吐量和延迟。虽然指标的含义人尽皆知,但是我寻思这些指标是写到 PPT 里给大老板看的。

latency 延迟的粒度

对于系统性能分析来说,这里也许有进一步讨论的价值:

  1. 从 IO 特征的角度来看,吞吐量描述了不同的 IO 块大小乘上对应的 IOPS 的累计结果。反过来说,吞吐量作为计算后的结果其实忽视了 IO 特征中块大小的分布信息。
  2. 延迟指标也有粒度上的问题,见上图,如果 IO 生命周期只有一个从开始到结束的延迟统计信息,那就难以发现是哪一个层面出现的性能问题。
  3. 无论是 IOPS、吞吐量还是延迟,它们都是针对已经完成的 IO 操作而言的,还没考虑到尚未完成的 IO 操作,因此还需要观察排队压力情况,也就是 IO 的队列长度
  4. 同样是 IO 特征,不同的业务场景会影响到 IO 的访问模式。如果存在异常的读写比例倾斜,我们可以直接定位是 IO 性能问题,节省对其它指标的分析时间。

上面加粗的文字部分是需要额外考虑的性能指标。

IO 块大小分布的获取方式很多:可以使用 bpftrace.biostacks 命令,输出还包含了调用的 IO 栈路径;也可以使用 bpftrace.bitesize 命令,输出包含了调用者的进程 ID。

IO 队列长度的获取可以使用 iostat -x 或者 sar -d 命令。aqu-sz 即所求。

IO 访问模式的获取可以使用 bcc.biopattern 命令,输出 %RND%SEQ 比例;以及使用 iostat -x 命令,读取 r/sw/s(按请求个数),rkB/swkB/s(按请求大小)。

延迟粒度如果需要简单获取,可以使用 bpftrace.biolatency 命令,提供 IO 的延迟时间和对应的 IO 个数汇总;复杂的就需要使用 blktrace+blkparse+btt 工具,会按照下表划分 IO 栈内部的延迟时间,其中 D2C 和 Q2C 是较为重要的指标,分别表示 IO 栈的硬件服务时间和 IO 栈的完整响应时间(等同于 bpftrace.biolatency 结果),另外 Q2G 也代表了 IO 排队的情况。

blkparse的延迟统计粒度:

 Q------->G------------>I--------->M------------------->D----------------------------->C
 |-Q time-|-Insert time-|
 |--------- merge time ------------|-merge with other IO|
 |----------------scheduler time time-------------------|---driver,adapter,storagetime--|
 
 |----------------------- await time in iostat output ----------------------------------|

Q2Q — time between requests sent to the block layer
Q2G — time from a block I/O is queued to the time it gets a request allocated for it
G2I — time from a request is allocated to the time it is Inserted into the device's queue
Q2M — time from a block I/O is queued to the time it gets merged with an existing request
I2D — time from a request is inserted into the device's queue to the time it is actually
      issued to the device
M2D — time from a block I/O is merged with an exiting request until the request is issued
      to the device
D2C — service time of the request by the device
Q2C — total time spent in the block layer for a request


You can deduce a lot about a workload from the above table. For example, if Q2Q is much
larger than Q2C, that means the application is not issuing I/O in rapid succession. Thus,
any performance problems you have may not be at all related to the I/O subsystem.

If D2C is very high, then the device is taking a long time to service requests. This can
indicate that the device is simply overloaded (which may be due to the fact that it is a
shared resource), or it could be because the workload sent down to the device is sub-optimal.

If Q2G is very high, it means that there are a lot of requests queued concurrently. This
could indicate that the storage is unable to keep up with the I/O load.

!!!!! WORK IN PROGRESS !!!!!
!!!!! WORK IN PROGRESS !!!!!
!!!!! WORK IN PROGRESS !!!!!

还有好多内容要写,比如上面只提了块设备用途的工具,bcc 还提供了 [fs_name]slower 工具直接定位到具体文件系统中超过一定时长的操作(如 f2fsslower);或者简单粗暴地找出暴力发出 IO 的 iotop 等等。其他类型的工具也没整理好,容我歇一会 orz

References

Systems Performance, 2nd Edition
Linux Load Averages: Solving the Mystery – Brendan Gregg’s Blog
CPU Utilization is Wrong – Brendan Gregg’s Blog
top 中 CPU 使用率计算原理 – 知乎
sar man page on SuSE – Polarhome
内存管理概览 – Android Developers
Block I/O Layer Tracing: blktrace – Hewlett-­Packard Company
6.3. Tools – Red Hat Customer Portal
blktrace 分析 IO – Bean Li
Block Layer Observability with bcc-tools – Oracle Linux Blog
tracepoints instead of kprobes in biolatency.bt – bpftrace
XRP: In-Kernel Storage Functions with eBPF – USENIX