作者 | 勿非

本文以体系为中心, 结合日常工作和用例, 由浅入深地介绍了功用剖析的一些办法和体会, 期望对想了解体系功用剖析的同学有所协助。

入门篇

资源视点

USE

产品跑在体系的各种资源上面, 从体系资源的视点入门功用剖析是个不错的挑选, 咱们以业界闻名大牛 Brendan Gregg 的 USE 办法开端, USE 特色便是简略有用适合入门, 用 Brendan 的话描绘 USE 的作用:

I find it solves about 80% of server issues with 5% of the effort.

USE 从体系资源的视点, 包含但不限于 CPU, 内存, 磁盘, 网络等, 重视以下3个方面:

• Utilization (U): as a percent over a time interval. eg, “one disk is running at 90% utilization”. 大多数状况能够合理估测运用率高或许会影响功用

• Saturation (S): as a queue length. eg, “the CPUs have an average run queue length of four”. 资源竞赛的激烈程度

• Errors (E). scalar counts. eg, “this network interface has had fifty late collisions”. Errors 相对直观

CPU

关于 CPU, 首要重视以下方针: • Utilization. CPU 的运用率 • Saturation. 能够是 load average, runqueue length, sched latency 等

CPU 运用率用 top 看下:

top - 17:13:49 up 83 days, 23:10,  1 user,  load average: 433.52, 422.54, 438.70
Tasks: 2765 total,  23 running, 1621 sleeping,   0 stopped,  34 zombie
%Cpu(s): 23.4 us,  9.5 sy,  0.0 ni, 65.5 id,  0.7 wa,  0.0 hi,  1.0 si,  0.0 st

CPU 运用率拆分红了更细粒度的几部分: • us, sys, ni – 对应 un-niced user, kernel, niced user 的 CPU 运用率 • id, wa – 对应到 idle, io wait 的份额, io wait 本质上也是一种 idle, 区别在于对应 cpu 上有等候 io 的使命 • hi, si – 对应 hardirq, softirq 的份额 • st – 由于超卖等原因, hypervisor 从该 vm 偷走的时刻 (todo: docker)

持续看 load average, 3 个数值别离对应到体系 1/5/15 分钟内的体系均匀 load, load 是个比较含糊的概念, 能够简略认为是对资源有需求的使命数, 包含 on cpu, runnable 的使命, 也包含等候 IO 及任意 D 状况的使命. load 运用采样的办法, 每隔 5 秒采样相同, 越近的采样权重越大, 这样从 1/5/15 的趋势能够看出体系压力的改变。

load average: 433.52, 422.54, 438.70

在这台 128 个 CPU 的机器上, loadavg 看起来有些偏高, 可是详细影响目前不得而知, 功用低是相对详细方针而言的, load 高仅仅现象, 它或许相关也或许无关, 但至少是值得注意的。

再看下 dstat 关于使命状况的核算: • run – 对应到/proc/stat 里边的 procs_running, 也便是 runnable 使命数 • blk – 对应到/proc/stat 里边的 procs_blocked, 阻塞在 I/O 的使命数

实际上和 loadavg 没有本质区别, 仅仅 load 含糊了 runnable 和 D 状况, 同时 load 运用 1/5/15 分钟的力度, 而 dstat 能够运用更细粒度, 假如只看某一时刻点用 load, 假如要调查长时刻的改变运用 dstat (/proc/stat)。

#dstat -tp
----system---- ---procs---
     time     |run blk new
07-03 17:56:50|204 1.0 202
07-03 17:56:51|212   0 238
07-03 17:56:52|346 1.0 266
07-03 17:56:53|279 5.0 262
07-03 17:56:54|435 7.0 177
07-03 17:56:55|442 3.0 251
07-03 17:56:56|792 8.0 419
07-03 17:56:57|504  16 152
07-03 17:56:58|547 3.0 156
07-03 17:56:59|606 2.0 212
07-03 17:57:00|770   0 186

内存

这儿首要重视内存容量方面, 不重视访存的功用。 • Utilization. 内存运用率 • Saturation. 这儿首要调查内存收回算法的功率

简略的内存运用率用 free 指令: • total – MemTotal + SwapTotal, 一般来说 MemTotal 会略小于真实的物理内存 • free – 未运用的内存. Linux 倾向于缓存更多页面以提高功用, 所以不能简略经过 free 来判别内存是否缺乏 • buff/cache – 体系缓存, 一般不需求严格区分 buffer 和 cache • available – 估量的可用物理内存巨细 • used – 等于 total – free – buffers – cache • Swap – 该机器上未装备

#free -g
              total        used        free      shared  buff/cache   available
Mem:            503         193           7           2         301         301
Swap:             0           0           0

更详细的信息能够直接去读/proc/meminfo:

#cat /proc/meminfo
MemTotal:       527624224 kB
MemFree:         8177852 kB
MemAvailable:   316023388 kB
Buffers:        23920716 kB
Cached:         275403332 kB
SwapCached:            0 kB
Active:         59079772 kB
Inactive:       431064908 kB
Active(anon):    1593580 kB
Inactive(anon): 191649352 kB
Active(file):   57486192 kB
Inactive(file): 239415556 kB
Unevictable:      249700 kB
Mlocked:          249700 kB
SwapTotal:             0 kB
SwapFree:              0 kB
[...]

再来看下内存收回相关的信息, sar 的数据首要从/proc/vmstat 采集, 首要重视: • pgscank/pgscand – 别离对应 kswapd/direct 内存收回时扫描的 page 数 • pgsteal – 收回的 page 数 • %vmeff – pgsteal/(pgscank+pgscand)

要了解这些数据的详细意义, 需求对内存办理算法有必定了解, 比方这儿的 pgscan/pgsteal 仅仅针对inactive list而言的, 在内存收回的时分或许还需求先把页面从 active list 搬到 inactive list 等. 假如这儿有反常, 咱们能够先把这当成进口, 再渐渐深化, 详细到这儿的%vmeff, 最好状况便是每个扫描的 page 都能收回, 也便是 vmeff 越高越好。

#sar -B 1
    11:00:16 AM     pgscank/s pgscand/s pgsteal/s    %vmeff
    11:00:17 AM          0.00      0.00   3591.00      0.00
    11:00:18 AM          0.00      0.00  10313.00      0.00
    11:00:19 AM          0.00      0.00   8452.00      0.00

I/O

存储 I/O 的 USE 模型: • Utilization. 存储设备的运用率, 单位时刻内设备在处理 I/O 恳求的时刻 • Saturation. 行列长度

咱们一般重视这些部分: • %util – 运用率. 注意即便到达 100%的 util, 也不代表设备没有功用余量了, 特别地现在的 SSD 盘内部都支撑并发. 打个比方, 一家旅馆有 10 间房, 每天只需有 1 个房间入住, util 便是 100%。 • svctm – 新版 iostat 现已删掉 • await/r_await/w_await – I/O 推迟, 包含排队时刻 • avgrq-sz – 均匀 request size, 恳求处理时刻和巨细有必定联系, 不必定线性 • argqu-sz – 评估 queue size, 能够用来判别是否有积压 • rMB/s, wMB/s, r/s, w/s – 根本语义

系统性能分析从入门到进阶

资源粒度

当咱们判别资源是否是瓶颈的时分, 只看体系等级的资源是不行的, 比方能够用 htop 看下每个 CPU 的运用率, 方针使命运转在不同 CPU 上的功用或许相差很大。

系统性能分析从入门到进阶

内存也有相似状况, 运转 numastat -m

                          Node 0          Node 1          Node 2          Node 3
                 --------------- --------------- --------------- ---------------
MemTotal                31511.92        32255.18        32255.18        32255.18
MemFree                  2738.79          131.89          806.50        10352.02
MemUsed                 28773.12        32123.29        31448.69        21903.16
Active                   7580.58          419.80         9597.45         5780.64
Inactive                17081.27        26844.28        19806.99        13504.79
Active(anon)                6.63            0.93            2.08            5.64
Inactive(anon)          12635.75        25560.53        12754.29         9053.80
Active(file)             7573.95          418.87         9595.37         5775.00
Inactive(file)           4445.52         1283.75         7052.70         4450.98

体系不必定便是物理机, 假如产品跑在 cgroup, 那么这个 cgroup 是更需求重视的体系, 比方在闲暇体系上履行如下指令:

#mkdir /sys/fs/cgroup/cpuset/overloaded
#echo 0-1 > /sys/fs/cgroup/cpuset/cpuset.cpus
#echo 0 > /sys/fs/cgroup/cpuset/cpuset.mems
#echo $$
#for i in {0..1023}; do /tmp/busy & done

此刻从物理机等级看, 体系的 load 很高, 可是由于 cpuset 的约束, 竞赛约束在 cpu 0 和 1 上, 对运转在其他 cpu 上的产品影响并不大。

#uptime
 14:10:54 up 6 days, 18:52, 10 users,  load average: 920.92, 411.61, 166.95

运用视点

体系资源和运用的功用或许会有某种相关, 可是也能够更直接地从运用的视点动身定位问题:

• 运用能运用多少资源, 而不是体系供给了多少资源, 这儿边或许会有gap, 体系是个含糊的概念, 而运用自身却相对详细. 以上面cpuset为例, 物理机是个体系, cpuset办理的资源也能够成为体系, 可是运用在cpuset里边仍是外面是确定的。

• 运用对资源的需求, 即便体系资源再多, 运用用不上功用也上不去, 也便是体系或许没问题, 而是运用自身的原因。

以下面的myserv为例, 它的4个线程%cpu都到达了100, 这个时分再去剖析整个体系的load什么用处不大, 体系有再多的闲暇cpu对myserv来说现已没有意义。

#pidstat -p `pgrep myserv` -t 1
15:47:05      UID      TGID       TID    %usr %system  %guest    %CPU   CPU  Command
15:47:06        0     71942         -  415.00    0.00    0.00  415.00    22  myserv
15:47:06        0         -     71942    0.00    0.00    0.00    0.00    22  |__myserv
...
15:47:06        0         -     72079    7.00   94.00    0.00  101.00    21  |__myserv
15:47:06        0         -     72080   10.00   90.00    0.00  100.00    19  |__myserv
15:47:06        0         -     72081    9.00   91.00    0.00  100.00    35  |__myserv
15:47:06        0         -     72082    5.00   95.00    0.00  100.00    29  |__myserv

常用指令

根本指令

根本指令一般用来读取内核中记载的各种核算信息, 特别是/proc下面的各种文件, 这儿简略罗列部分: • top – 供给了交互形式和batch形式, 不带参数进入交互形式, 按下h键能够看到各种功用 • ps – 供给了各种参数检查体系中使命的状况, 比方ps aux或许ps -eLf, 许多参数能够在需求的时分检查手册 • free – 内存信息 • iostat – I/O功用 • pidstat – 检查进程相关的信息, 上面现已介绍过 • mpstat – 能够检查独自cpu的运用率, softirq, hardirq个数等 • vmstat – 能够检查虚拟内存及各种体系信息 • netstat – 网络相关 • dstat – 能够检查cpu/disk/mem/net等各种信息, 这些stat指令哪个便运用哪个 • htop – 上面介绍过 • irqstat – 便利调查中止信息 • sar/tsar/ssar – 搜集和检查体系运转的各种历史信息, 也供给实时形式

这儿举个ps的比方, 咱们监控mysqld服务, 当该进程运用的内存超越体系内存70%的时分, 经过gdb调用jemalloc的malloc_stats_print函数来剖析或许的内存泄漏。

largest=70
while :; do
    mem=$(ps -p `pidof mysqld` -o %mem | tail -1)
    imem=$(printf %.0f $mem)
    if [ $imem -gt $largest ]; then
        echo 'p malloc_stats_print(0,0,0)' | gdb --quiet -nx -p `pidof mysqld`
    fi
    sleep 10
done

perf

perf是功用剖析的必备东西, 它最中心的才能是能拜访硬件上的Performance Monitor Unit (PMU), 对剖析CPU bound的问题很有协助, 当然perf也支撑各种软件event. perf的首要才能包含: • 经过采样发现程序热门 • 经过硬件PMU深化剖析问题的根源, 特别是配合硬件上的优化 perf list能够列出支撑的event, 咱们能够经过perf来获取cache misses, cycles等等。

#perf list | grep Hardware
  branch-misses                                      [Hardware event]
  bus-cycles                                         [Hardware event]
  cache-misses                                       [Hardware event]
  cache-references                                   [Hardware event]
  cpu-cycles OR cycles                               [Hardware event]
  instructions                                       [Hardware event]
  L1-dcache-load-misses                              [Hardware cache event]
  L1-dcache-loads                                    [Hardware cache event]
  L1-dcache-store-misses                             [Hardware cache event]
  L1-dcache-stores                                   [Hardware cache event]
  L1-icache-load-misses                              [Hardware cache event]
  L1-icache-loads                                    [Hardware cache event]
  branch-load-misses                                 [Hardware cache event]
  branch-loads                                       [Hardware cache event]
  dTLB-load-misses                                   [Hardware cache event]
  iTLB-load-misses                                   [Hardware cache event]
  mem:<addr>[/len][:access]                          [Hardware breakpoint]

perf运用的时分一般会传入以下参数: • 经过-e指定感兴趣的一个或多个event • 指定采样的规模, 比方进程等级 (-p), 线程等级 (-t), cpu等级 (-C), 体系等级 (-a)

这儿运用默许的event看下进程31925的履行状况. 一个比较重要的信息是insns per cycle (IPC), 也便是每个cycle能履行多少指令, 其他pmu event像cache misses, branch misses假如有问题终究都会反映到IPC上. 尽管没有一个明确的规范, 可是下面0.09的IPC是比较低的, 有必要持续深化。

#perf stat -p 31925  sleep 1
 Performance counter stats for process id '31925':
       2184.986720      task-clock (msec)         #    2.180 CPUs utilized
             3,210      context-switches          #    0.001 M/sec
               345      cpu-migrations            #    0.158 K/sec
                 0      page-faults               #    0.000 K/sec
     4,311,798,055      cycles                    #    1.973 GHz
   <not supported>      stalled-cycles-frontend
   <not supported>      stalled-cycles-backend
       409,465,681      instructions              #    0.09  insns per cycle
   <not supported>      branches
         8,680,257      branch-misses             #    0.00% of all branches
       1.002506001 seconds time elapsed

除了stat外, perf另一个或许更常用的办法是采样来确定程序的热门, 现在有如下程序:

void busy(long us) {
    struct timeval tv1, tv2;
    long delta = 0;
    gettimeofday(&tv1, NULL);
    do {
        gettimeofday(&tv2, NULL);
        delta = (tv2.tv_sec - tv1.tv_sec) * 1000000 + tv2.tv_usec - tv1.tv_usec;
    } while (delta < us);
}
void A() { busy(2000); }
void B() { busy(8000); }
int main() {
    while (1) {
        A(); B();
    }
    return 0;
}

函数A和B履行时刻的份额, perf的采样成果和咱们期望的2:8根本共同。

#perf record -g -e cycles ./a.out
#perf report
Samples: 27K of event 'cycles', Event count (approx.): 14381317911
  Children      Self  Command  Shared Object     Symbol
+   99.99%     0.00%  a.out    [unknown]         [.] 0x0000fffffb925137
+   99.99%     0.00%  a.out    a.out             [.] _start
+   99.99%     0.00%  a.out    libc-2.17.so      [.] __libc_start_main
+   99.99%     0.00%  a.out    a.out             [.] main
+   99.06%    25.95%  a.out    a.out             [.] busy
+   79.98%     0.00%  a.out    a.out             [.] B
-   71.31%    71.31%  a.out    [vdso]            [.] __kernel_gettimeofday
     __kernel_gettimeofday
   - busy
      + 79.84% B
      + 20.16% A
+   20.01%     0.00%  a.out    a.out             [.] A

strace

trace相关于采样最大的优势在于精度, trace能抓住每次操作, 这给调试和了解带来很大便利. strace专门用来trace体系调用。

strace经过捕获一切的体系调用能快速协助了解运用的某些行为, 这儿运用strace来看下上面说到的perf-record的完成, 很简略发现体系调用perf_event_open以及它的参数, 由于有128个cpu, 针对每个cpu都会调用一次该体系调用。

#strace -v perf record -g -e cycles ./a.out
perf_event_open({type=PERF_TYPE_HARDWARE, size=PERF_ATTR_SIZE_VER5, config=PERF_COUNT_HW_CPU_CYCLES, sample_freq=4000, sample_type=PERF_SAMPLE_IP|PERF_SAMPLE_TID|PERF_SAMPLE_TIME|PERF_SAMPLE_CALLCHAIN|PERF_SAMPLE_PERIOD, read_format=0, disabled=1, inherit=1, pinned=0, exclusive=0, exclusive_user=0, exclude_kernel=0, exclude_hv=0, exclude_idle=0, mmap=1, comm=1, freq=1, inherit_stat=0, enable_on_exec=1, task=1, watermark=0, precise_ip=0 /* arbitrary skid */, mmap_data=0, sample_id_all=1, exclude_host=0, exclude_guest=1, exclude_callchain_kernel=0, exclude_callchain_user=0, mmap2=1, comm_exec=1, use_clockid=0, context_switch=0, write_backward=0, namespaces=0, wakeup_events=0, config1=0, config2=0, sample_regs_user=0, sample_regs_intr=0, aux_watermark=0, sample_max_stack=0}, 51876, 25, -1, PERF_FLAG_FD_CLOEXEC) = 30

blktrace

iostat由于粒度太粗有的时分并不能很好地定位问题, blktrace经过盯梢每个I/O, 并在I/O的要害途径打桩, 能够取得更准确的信息, 从而协助剖析问题. blktrace封装了几个指令: • blktrace: 搜集 • blkparse: 处理 • btt: 强大的剖析东西 • btrace: blktrace/blkparse的一个简略封装, 相当于blktrace -d /dev/sda -o – | blkparse -i –

简略看下blktrace的输出, 里边记载了I/O途径上的要害信息, 特别地: • 时刻戳, 功用剖析的要害信息之一 • event, 第6列, 对应到I/O途径上的要害点, 详细对应联系能够查找相应手册或源码, 了解这些要害点是调试I/O功用的必要技术 • I/O sector. I/O恳求对应的扇区和巨细

$ sudo btrace /dev/sda
8,0    0        1     0.000000000  1024  A  WS 302266328 + 8 <- (8,5) 79435736
8,0    0        2     0.000001654  1024  Q  WS 302266328 + 8 [jbd2/sda5-8]
8,0    0        3     0.000010042  1024  G  WS 302266328 + 8 [jbd2/sda5-8]
8,0    0        4     0.000011605  1024  P   N [jbd2/sda5-8]
8,0    0        5     0.000014993  1024  I  WS 302266328 + 8 [jbd2/sda5-8]
8,0    0        0     0.000018026     0  m   N cfq1024SN / insert_request
8,0    0        0     0.000019598     0  m   N cfq1024SN / add_to_rr
8,0    0        6     0.000022546  1024  U   N [jbd2/sda5-8] 1

这是btt的一个输出, 能够看到S2G的个数和推迟, 正常状况不应该出现这个问题, 这样就找到了一条能够深化的头绪。

$ sudo blktrace -d /dev/sdb -w 5
$ blkparse sdb -d sdb.bin
$ btt -i sdb.bin
==================== All Devices ====================
            ALL           MIN           AVG           MAX           N
--------------- ------------- ------------- ------------- -----------
Q2Q               0.000000001   0.000014397   0.008275391      347303
Q2G               0.000000499   0.000071615   0.010518692      347298
S2G               0.000128160   0.002107990   0.010517875       11512
G2I               0.000000600   0.000001570   0.000040010      347298
I2D               0.000000395   0.000000929   0.000003743      347298
D2C               0.000116199   0.000144157   0.008443855      347288
Q2C               0.000118211   0.000218273   0.010678657      347288
==================== Device Overhead ====================
       DEV |       Q2G       G2I       Q2M       I2D       D2C
---------- | --------- --------- --------- --------- ---------
 (  8, 16) |  32.8106%   0.7191%   0.0000%   0.4256%  66.0447%
---------- | --------- --------- --------- --------- ---------
   Overall |  32.8106%   0.7191%   0.0000%   0.4256%  66.0447%

进阶篇

大学教材

经过教程能够体系地了解一门课的全貌, 网上搜到的大部分功用剖析的教程都是根据Raj Jain的The Art of Computer Systems Performance Analysis, 这本书里边首要包含几个部分:

• Part I: AN OVERVIEW OF PERFORMANCE EVALUATION • Part II: MEASUREMENT TECHNIQUES AND TOOLS • Part III: PROBABILITY THEORY AND STATISTICS • Part IV: EXPERIMENTAL DESIGN AND ANALYSIS • Part V: SIMULATION • Part VI: QUEUEING MODELS

书的重心放在performance analysis上面, 触及较多概率和核算的核算, 别的rice大学的这个教程写得挺不错[1]。

技术博客

• 参考文末[2]有时刻能够都过一遍, 总的来说首要包含3个部分: • 功用剖析的办法集. 代表作 USE办法 • 功用数据的搜集. 代表作 “东西大图” • 功用数据的可视化. 代表作 火焰图 • 文末链接[3] • 文末链接[4] • 文末链接[5]

常识结构

体系功用剖析在深度和广度上都有要求, 对底层包含OS和硬件, 以及一些通用才能要做到满足深, 对上层产品的了解又需求有满足的广度, 近一年在混合云亲手摸过的产品估量不下二十款, 当然要点剖析过的只要几个。

操作体系

操作体系是体系剖析的基础, 不管是I/O, 内存, 网络, 调度, docker等等都离不开操作体系, 操作体系常识能够从Understanding the Linux Kernel开端, 这本书尽管老了但不妨碍了解OS的根本概念, 渐渐做到能阅读内核文档和源码。

在适配某款arm渠道的时分发现, 在numa off的状况下: • ecs绑在socket 0上功用好 • mysql绑在socket 1上功用好

能确定的是, 该渠道跨socket功用拜访不管是latency仍是throughput和本地拜访都有较大差距, 所以一个合理的方向是跨socket的内存拜访, 假如有相似x86 pcm的话会比较直接, 可是该渠道上短少该类pmu来检查跨socket的信息, 咱们尝试从OS的视点来答复这个问题。

首要经过将内存压测东西跑在不同的socket/node上, 发现numa off体现出了和numa on相同的功用特征, 和硬件产生承认该渠道numa off和on的完成在硬件上并没有区别, 仅仅bios不传递numa信息给操作体系, 这样是能够知道物理地址在哪个socket/node上的。

接下来只需确定ecs/mysql的物理内存位置, 就能够用于判别功用和跨socket的相关性. Linux在用户态能够经过pagemap将虚拟地址对应到物理地址, 只需求稍加修正tools/vm/page-types.c就能拿到进程对应的一切物理地址. 经承认, 确实ecs/mysql的功用和它们运用的物理内存的位置强相关。终究要答复的是为什么ecs和mysql体现恰好相反, 注意到ecs运用hugepage而mysql运用normal page, 有如下假定, 详细代码这儿不再列出。

• 体系启动的时分, 物理内存加到同伴体系是先socket 0后socket 1 • socket 1上的内存会被先分出来, 所以mysql分配的内存在socket 1. 特定集群的机器不会随意跑其他进程 • 在ecs的host上, 由于要分配的hugepage现已超越了socket 1上的一切内存, 所以后面分配的hugepage现已落在了socket 0 • hugepage的分配是后进先出, 意味着ecs一开端分配到的hugepage在socket 0, 而该机器资源并没全部用完, 测验用的几个ecs内存全落在了socket 0上, 所以将ecs进程绑到socket 0的功用更好

硬件常识

假如一直是x86架构, 事情会简略许多, 一是x86的常识咱们耳濡目染很久了, 多多少少都了解一些, 二是架构改变相对较小, 各种运用都适配较好, 需求调优的用例较少. 跟着各种新渠道的崛起, 它们功用各异, 对整个体系功用带来的冲击是巨大的, 这不是影响某个产品, 这影响的简直是一切产品. 最根本地, 咱们要处理以下问题:

• 新的渠道上, 运用原有的许多假定被打破, 需求从头适配, 否则功用或许不及预期. 比方在Intel上面, 开关numa的功用差距不大, 在其他渠道上或许就不相同 • 新的渠道要取代老的渠道, 就存在功用的比较. 由于渠道功用差异大而且差异点多, 尽管speccpu之类的benchmark能必定程度反应渠道全体的核算功用, 但许多时分还需求结合不同场景别离进行功用调优 • 不排除新渠道存在某种bug或许不知道的feature, 都需求咱们去探索处理的办法

数据剖析

在搜集了许多数据后, 经过数据剖析能够放大数据的价值 • 数据提取. 运用各种东西比方awk/sed/perl等脚本语言提取所需的数据 • 数据抽象. 从不同视点加工数据, 识别反常, 比方单机/集群别离是什么体现, 核算哪些值 • 可视化. 可视化是数据处理非常重要的才能, 一图胜千言, 火焰图便是最好的比方. 常用画图东西有gnuplot, excel等

比方剖析MapReduce使命在10台机器的集群上的功用, 即便每台机器都体现出必定的共性, 可是假如从集群视点看的话则愈加显着, 也很简略验证这种共性。

系统性能分析从入门到进阶

换种显现办法则愈加显着, 很简略知道在不同阶段的体现, 比方正常Map和Reduce阶段cpu运用率也只要80%, 这个是否契合预期, 别的在Map和Reduce切换的时分, 体系idle很显着, 会不会是潜在优化点。

系统性能分析从入门到进阶

假如有对照的话, 能够直观地看不到不同体现, 特别是巨大的长尾时刻有进一步优化的空间。

系统性能分析从入门到进阶

Benchmarking

Benchmarking是获取功用方针最根本的手法, 也是测验常用的办法, 每个范畴简直都有自己的一套测验用例. 关于benchmarking, 首要需求知道它测的是什么. 以spec cpu2017为例, 它首要测验的是处理器, 内存子体系以及编译器的功用, 那么在测验的时分咱们除了重视CPU型号, 还要考虑内存巨细插法型号, 以及编译器及其参数等等, 在做功用比对时也能清楚它的运用规模。

Benchmark的一个特色是可重复性, spec.org做得很好的一点是上面有许多公布的测验成果, 能够参考这些测验成果来验证咱们自己的测验办法参数是否合理. 假如想测验cpu2017, 第一件事便是先重做别人的测验, 直到能复现别人的数据, 这个进程或许会有许多收成, 对benchmark也会有更多了解. 以intel 8160为例, 在硬件根本共同的状况下, 不经额定的装备自己环境cpu2017 integer rate只能跑到140, 而spec.org上面的测验用例能到达240, 功用逐步逼近240的进程, 也是深化了解cpu2017的进程。

关于功用数据, 首要想要强调的是有数据并不必定比没数据强, 只要解说过的数据才是有用数据, 没解说过的数据反而会引起不必要的误判, 比方上面cpu2017的比方, 在做不同渠道功用对比的时分, 8160到底用140仍是240呢, 得出的定论会十万八千里. 再比方运用下面的指令测验某新渠道的内存推迟:

lat_mem_rd -P 1 -N 1 10240 512

测验出的推迟是7.4ns, 不加剖析选用该成果就或许得出新渠道推迟太好的过错定论. 所以对待数据要满足谨慎, 一般会有几个阶段:

  1. 在信赖联系树立前, 对别人的数据坚持谨慎. 一是有或许自己对这块还没有满足了解, 二是需求测验报告供给满足的信息供别人做判别。
  2. 信任自己的数据. 有必要信任自己, 可是挑选信任自己的数据, 是由于有过详细合理的剖析。
  3. 信任别人的数据. 信赖链树立之后, 以及自己有了满足了解后, 挑选信任.

更多东西

ftrace

想要快速了解代码完成, 没有什么比打印调用途径更直接了. ftrace能够用来处理2个问题: • 谁调用了我. 这个只需在履行对应函数的时分拿到对应的栈就能够, 多种东西能够完成 • 我调用了谁. 这个是ftrace比较unique的功用 为了便利咱们运用ftrace的wrapper trace-cmd, 假定咱们现已知道I/O途径会经过generic_make_request, 为了检查完好的途径咱们能够这样:

#trace-cmd record -p function --func-stack -l generic_make_request dd if=/dev/zero of=file bs=4k count=1 oflag=direct

经过report来检查就一目了然了:

#trace-cmd report
cpus=128
              dd-11344 [104] 4148325.319997: function:             generic_make_request
              dd-11344 [104] 4148325.320002: kernel_stack:         <stack trace>
=> ftrace_graph_call (ffff00000809849c)
=> generic_make_request (ffff000008445b80)
=> submit_bio (ffff000008445f00)
=> __blockdev_direct_IO (ffff00000835a0a8)
=> ext4_direct_IO_write (ffff000001615ff8)
=> ext4_direct_IO (ffff0000016164c4)
=> generic_file_direct_write (ffff00000825c4e0)
=> __generic_file_write_iter (ffff00000825c684)
=> ext4_file_write_iter (ffff0000016013b8)
=> __vfs_write (ffff00000830c308)
=> vfs_write (ffff00000830c564)
=> ksys_write (ffff00000830c884)
=> __arm64_sys_write (ffff00000830c918)
=> el0_svc_common (ffff000008095f38)
=> el0_svc_handler (ffff0000080960b0)
=> el0_svc (ffff000008084088)

现在假如咱们想持续深化generic_make_request, 运用function_graph plugin:

$ sudo trace-cmd record -p function_graph -g generic_make_request dd if=/dev/zero of=file bs=4k count=1 oflag=direct

这样就能够拿到整个调用进程 (report成果略微整理过):

$ trace-cmd report
    dd-22961                 |  generic_make_request() {
    dd-22961                 |    generic_make_request_checks() {
    dd-22961      0.080 us   |      _cond_resched();
    dd-22961                 |      create_task_io_context() {
    dd-22961      0.485 us   |        kmem_cache_alloc_node();
    dd-22961      0.042 us   |        _raw_spin_lock();
    dd-22961      0.039 us   |        _raw_spin_unlock();
    dd-22961      1.820 us   |      }
    dd-22961                 |      blk_throtl_bio() {
    dd-22961      0.302 us   |        throtl_update_dispatch_stats();
    dd-22961      1.748 us   |      }
    dd-22961      6.110 us   |    }
    dd-22961                 |    blk_queue_bio() {
    dd-22961      0.491 us   |      blk_queue_split();
    dd-22961      0.299 us   |      blk_queue_bounce();
    dd-22961      0.200 us   |      bio_integrity_enabled();
    dd-22961      0.183 us   |      blk_attempt_plug_merge();
    dd-22961      0.042 us   |      _raw_spin_lock_irq();
    dd-22961                 |      elv_merge() {
    dd-22961      0.176 us   |        elv_rqhash_find.isra.9();
    dd-22961                 |        deadline_merge() {
    dd-22961      0.108 us   |          elv_rb_find();
    dd-22961      0.852 us   |        }
    dd-22961      2.229 us   |      }
    dd-22961                 |      get_request() {
    dd-22961      0.130 us   |        elv_may_queue();
    dd-22961                 |        mempool_alloc() {
    dd-22961      0.040 us   |          _cond_resched();
    dd-22961                 |          mempool_alloc_slab() {
    dd-22961      0.395 us   |            kmem_cache_alloc();
    dd-22961      0.744 us   |          }
    dd-22961      1.650 us   |        }
    dd-22961      0.334 us   |        blk_rq_init();
    dd-22961      0.055 us   |        elv_set_request();
    dd-22961      4.565 us   |      }
    dd-22961                 |      init_request_from_bio() {
    dd-22961                 |        blk_rq_bio_prep() {
    dd-22961                 |          blk_recount_segments() {
    dd-22961      0.222 us   |            __blk_recalc_rq_segments();
    dd-22961      0.653 us   |          }
    dd-22961      1.141 us   |        }
    dd-22961      1.620 us   |      }
    dd-22961                 |      blk_account_io_start() {
    dd-22961      0.137 us   |        disk_map_sector_rcu();
    dd-22961                 |        part_round_stats() {
    dd-22961      0.195 us   |          part_round_stats_single();
    dd-22961      0.054 us   |          part_round_stats_single();
    dd-22961      0.955 us   |        }
    dd-22961      2.148 us   |      }
    dd-22961    + 15.847 us  |    }
    dd-22961    + 23.642 us  |  }

uftrace

uftrace在用户态完成了一个相似ftrace的功用, 对需求快速了解用户态的逻辑会有协助, 可是需求加上-pg从头编译源码, 详情见[6]。

#gcc -pg a.c
#uftrace ./a.out
# DURATION     TID     FUNCTION
            [ 69439] | main() {
            [ 69439] |   A() {
   0.160 us [ 69439] |     busy();
   1.080 us [ 69439] |   } /* A */
            [ 69439] |   B() {
   0.050 us [ 69439] |     busy();
   0.240 us [ 69439] |   } /* B */
   1.720 us [ 69439] | } /* main */

BPF

BPF (eBPF) 是这几年的热门, 经过BPF简直能够看清体系的各个角落, 给诊断带来了极大的便利. BPF不是一个东西, BPF是生产东西的东西, BPF东西编写是功用剖析有必要把握的技术之一。

这儿举个运用BPF来剖析QEMU I/O推迟的比方. 为了简化问题, 先保证vm里边的块设备只要fio在运用, fio控制设备只要一个并发I/O, 这样咱们在host上挑选2个调查点: • tracepoint:kvm:kvm_mmio. host捕获guest mmio操作, guest里边终究经过写该mmio发送恳求给host • kprobe:kvm_set_msi. 由于guest里边vdb运用msi中止, 中止终究经过该函数注入

由于host上有多个vm和虚拟盘需求区分, 运用以下信息捕获而且只捕获咱们重视的这个设备: • 只重视该qemu-kvm pid • vbd mmio对应的gpa, 这个能够在guest里边经过lspci取得 关于kvm_set_msi, 运用如下信息: • struct kvm的userspace_pid, struct kvm对应的qemu-kvm进程 • struct kvm_kernel_irq_routing_entry的msi.devid, 对应到pci设备id

#include <linux/kvm_host.h>
BEGIN {
    @qemu_pid = $1;
    @mmio_start = 0xa000a00000;
    @mmio_end = 0xa000a00000 + 16384;
    @devid = 1536;
}
tracepoint:kvm:kvm_mmio /pid == @qemu_pid/ {
    if (args->gpa >= @mmio_start && args->gpa < @mmio_end) {
        @start = nsecs;
    }
}
kprobe:kvm_set_msi {
    $e = (struct kvm_kernel_irq_routing_entry *)arg0;
    $kvm = (struct kvm *)arg1;
    if (@start > 0 && $kvm->userspace_pid == @qemu_pid && $e->msi.devid == @devid) {
        @dur = stats(nsecs - @start);
        @start = 0;
    }
}
interval:s:1 {
    print(@dur); clear(@dur);
}

履行成果如下:

@dur: count 598, average 1606320, total 960579533
@dur: count 543, average 1785906, total 969747196
@dur: count 644, average 1495419, total 963049914
@dur: count 624, average 1546575, total 965062935
@dur: count 645, average 1495250, total 964436299

更深了解

许多技术需求反复去了解验证, 每一次或许都有不同的收成, 这儿举个loadavg的比方. 引用kernel/sched/loadavg.c最开端的一段注释:

  5  * This file contains the magic bits required to compute the global loadavg
  6  * figure. Its a silly number but people think its important. We go through
  7  * great pains to make it work on big machines and tickless kernels.

这儿的silly我想说的是loadavg有必定的局限性, 总的来说loadavg是有必定语义和价值的, 毕竟它只用了3个数字描绘了过去一段时刻的”load”, 反过来说假如loadavg is silly, 那么有没有更好的挑选? • 假如是实时调查的话, vmstat/dstat输出的runnable和I/O blocked的信息是种更好的挑选, 由于相关于loadavg每5秒的采样, vmstat能够做到粒度更细, 而且loadavg的算法某种程度能够了解为有损的。 • 假如是sar/tsar的话, 假定搜集距离是10min的话, loadavg由于能掩盖更大的规模, 确实比10min一个的数字包含更多的信息, 但咱们需求思考它对调试的真正价值.

别的, 5秒钟的采样距离是比较大的, 咱们能够结构个测验用例履行了许多时刻但越过采样 • 获取load采样点的时刻 • 测验用例刚好越过该采样点 检查calc_load_fold_active在cpu 0上的调用时刻:

kprobe:calc_load_fold_active /cpu == 0/ {
    printf("%ld\n", nsecs / 1000000000);
}

运转没有输出, 监控上一层函数:

#include "kernel/sched/sched.h"
kprobe:calc_global_load_tick /cpu == 0/ {
    $rq = (struct rq *)arg0;
    @[$rq->calc_load_update] = count();
}
interval:s:5 {
    print(@); clear(@);
}

履行成果契合预期:

#./calc_load.bt -I /kernel-source
@[4465886482]: 61
@[4465887733]: 1189
@[4465887733]: 62
@[4465888984]: 1188

检查汇编发现这儿代码被优化, 可是刚好id_nr_invalid调用没优化, 当然最便利的是能够直接在函数偏移处直接打点:

kprobe:id_nr_invalid /cpu == 0/ {
    printf("%ld\n", nsecs / 1000000000);
}

根据这个时刻戳, 能够很简略越过load的核算:

while :; do
        sec=$(awk -F. '{print $1}' /proc/uptime)
        rem=$((sec % 5))
        if [ $rem -eq 2 ]; then # 1s after updating load
                break;
        fi
        sleep 0.1
done
for i in {0..63}; do
        ./busy 3 & # run 3s
done

系统性能分析从入门到进阶

许多busy进程成功越过load的核算, 能够设想像cron履行的使命也是有这个或许的. 尽管不能否认loadavg的价值, 但总的来说load有以下缺点: • 体系等级的核算, 和详细运用产生的联系不行直接 • 运用采样的办法而且采样距离 (5s) 较大, 有的场景不能真实反映体系 • 核算的距离较大(1/5/15分钟), 不利于及时反映其时的状况 • 语义略微不行明晰, 不只包含cpu的load, 还包含D状况的使命, 这个自身不是大问题, 更多能够认为是feature

Linux增加了Pressure Stall Information (PSI), PSI从使命的视点别离核算了10/60/300s内由于cpu/io/memory等资源缺乏而不能运转的时长, 并依照影响规模分红2类: • some – 由于短少资源导致部分使命不能履行 • full – 由于短少资源导致一切使命不能履行, cpu不存在这种状况

咱们在一台96c的arm机器上扫描一切cgroup的cpu.pressure:

系统性能分析从入门到进阶

这儿会引出几个问题, 篇幅原因这儿不再打开。 • 父cgroup的avg为什么比子cgroup还小? 是完成问题仍是有额定的装备参数? • avg10等于33, 也便是1/3的时刻有task由于没有cpu而得不到履行, 考虑到体系cpu运用率在40%左右并不算高, 咱们怎样合理看待和运用这个值

top - 09:55:41 up 127 days,  1:44,  1 user,  load average: 111.70, 87.08, 79.41
Tasks: 3685 total,  21 running, 2977 sleeping,   1 stopped,   8 zombie
%Cpu(s): 27.3 us,  8.9 sy,  0.0 ni, 59.8 id,  0.1 wa,  0.0 hi,  4.0 si,  0.0 st

RTFSC

有的时分RTFM现已不行了, 手册包含东西自身的更新没对上内核的节奏, 咱们回到上面页面收回的比方, 估量有的同学之前就有疑问, 没有scan哪里来的steal。

#sar -B 1
    11:00:16 AM     pgscank/s pgscand/s pgsteal/s    %vmeff
    11:00:17 AM          0.00      0.00   3591.00      0.00
    11:00:18 AM          0.00      0.00  10313.00      0.00
    11:00:19 AM          0.00      0.00   8452.00      0.00

先看sysstat (sar) 里边的完成, 首要是读取剖析/proc/vmstat: • pgscand: 对应到pgscan_direct域 • pgscank: 对应到pgscan_kswapd域 • pgsteal: 对应到pgsteal_最初的域

#gdb --args ./sar -B 1
(gdb) b read_vmstat_paging
(gdb) set follow-fork-mode child
(gdb) r
Breakpoint 1, read_vmstat_paging (st_paging=0x424f40) at rd_stats.c:751
751             if ((fp = fopen(VMSTAT, "r")) == NULL)
(gdb) n
754             st_paging->pgsteal = 0;
(gdb)
757             while (fgets(line, sizeof(line), fp) != NULL) {
(gdb)
759                     if (!strncmp(line, "pgpgin ", 7)) {
(gdb)
763                     else if (!strncmp(line, "pgpgout ", 8)) {
(gdb)
767                     else if (!strncmp(line, "pgfault ", 8)) {
(gdb)
771                     else if (!strncmp(line, "pgmajfault ", 11)) {
(gdb)
775                     else if (!strncmp(line, "pgfree ", 7)) {
(gdb)
779                     else if (!strncmp(line, "pgsteal_", 8)) {
(gdb)
784                     else if (!strncmp(line, "pgscan_kswapd", 13)) {
(gdb)
789                     else if (!strncmp(line, "pgscan_direct", 13)) {
(gdb)
757             while (fgets(line, sizeof(line), fp) != NULL) {
(gdb)

看下/proc/vmstat都有什么:

#grep pgsteal_ /proc/vmstat
pgsteal_kswapd 168563
pgsteal_direct 0
pgsteal_anon 0
pgsteal_file 978205
#grep pgscan_ /proc/vmstat
pgscan_kswapd 204242
pgscan_direct 0
pgscan_direct_throttle 0
pgscan_anon 0
pgscan_file 50583828

终究看看内核的完成, pgsteal和pgscan的逻辑是相同, 除了nr_scanned换成了nr_reclaimed:

现在问题很明晰了: • 这儿sar取得是体系的/proc/vmstat, 而cgroup里边pgscan_kswapd和pgscan_direct只会加到cgroup的核算, 不会加到体系级的核算 • cgroup里边pgsteal_kswapd和pgsteal_direct同样只会加到cgroup自己的核算 • 可是首要pgscan_anon, pgscan_file和pgsteal_anon, pgsteal_file都只加到体系级的核算 • sar读取了pgscan_kswapd, pgscan_direct, 以及pgsteal_, 这儿还包含了pgsteal_anon和pgsteal_file

这整个逻辑都乱了, 咱们有必要处理这个bug让sar的输出变得愈加有意义. 那么在cgroup内是不是没问题?

#df -h .
Filesystem      Size  Used Avail Use% Mounted on
cgroup             0     0     0    - /sys/fs/cgroup/memory
#grep -c 'pgscan\|pgsteal' memory.stat
0

这些核算信息在cgroup v1上彻底没有输出, 而只在v2版别有输出. 在曾经内核没有专门LRU_UNEVICTABLE的时分, 假如有许多比方mlock page的时分, 碰到过不停扫描却不能收回内存的状况, 这个核算会非常有用, 即便是现在我信任这个核算仍是有用的, 仅仅大部分时分还不必看到这么细。

多上手

纸上得来终觉浅, 自己着手去做带来许多好处: • 答复预设问题. 调试剖析便是不断提出问题和验证的进程, 没有上手的话就会一直停留在第一个问题上. 比方我想了解某渠道上物理内存是怎样编址的, 没有文档的话只能自己去实验 • 提出新的问题. 调试剖析中不怕有问题, 怕的是提不出问题 • 会有意外收成. 许多时分并不是有意为之, 比方准备的是剖析cpu调频能否降功耗, 上去却发现体系一直运转在最低频率 • 娴熟. 娴熟便是功率 • 改进产品. 能够试想下在整个云环境一切机器上扫描 (相似全面体检) 会发现多少潜在问题

参考资料

[1]www.cs.rice.edu/~johnmc/com… [2]brendangregg.com/ [3]dtrace.org/blogs/bmc/ [4]blog.stgolabs.net/ [5]lwn.net/ [6]github.com/namhyung/uf… [7]www.brendangregg.com/[8]The Art of Computer Systems Performance Analysis