内存分析工具主要包含日常常用工具、systemtap和bpftrace工具等。其中常用工具主要包括:vmstat、wapon、sar、slabtop、numastat、ps、top、pmap等。

内存分析常用工具

vmstat

vmstat是一个虚拟内存统计工具,提供了一个上层视角的内存监控状态,包含空闲内存和换页统计;其中cpu相关的参数在之前已经讲解过。

  1. ~# vmstat 1
  2. procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
  3. r b swpd free buff cache si so bi bo in cs us sy id wa st
  4. 1 0 0 574936 155544 6819876 0 0 0 1 0 0 0 0 100 0 0
  5. 0 0 0 574888 155544 6819964 0 0 0 0 102 100 0 0 100 0 0
  6. 0 0 0 595600 155544 6819964 0 0 0 0 185 161 0 1 99 0 0
  7. 0 0 0 596136 155544 6819964 0 0 0 0 116 101 0 0 100 0 0
  8. 0 0 0 596192 155552 6819964 0 0 0 56 155 217 0 0 100 0 0
  9. 0 0 0 596160 155552 6819964 0 0 0 0 114 109 0 0 100 0 0
  10. 0 0 0 596192 155552 6819964 0 0 0 0 101 89 0 0 100 0 0

命令输出的第一行不是系统从启动到现在的数据,而是当前时刻的数据。以下列的默认值是kilobytes:

  1. swpd:换出内存的总量
  2. free:空闲内存的总量
  3. buff:buffer缓存的内存总量
  4. cache:页缓存的内存总量
  5. si:换入内存总量(换页paging)
  6. so:换出内存总量(换页paging)

如果感觉输出的格式后面比较乱,对不齐,可以使用-S参数进行格式输出(M代表1024进制MB,m代表1000进制MB),如下所示。
1024进制

~# vmstat -SM 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0    582    151   6660    0    0     0     1    0    0  0  0 100  0  0
 0  0      0    582    151   6660    0    0     0     0   86   73  0  0 100  0  0
 0  0      0    582    151   6660    0    0     0     0   54   66  0  0 100  0  0
 0  0      0    582    151   6660    0    0     0     0   74   81  0  0 100  0  0
 0  0      0    582    151   6660    0    0     0     0   51   61  0  0 100  0  0
 0  0      0    582    151   6660    0    0     0     0   42   45  0  0 100  0  0

1000进制

~# vmstat -Sm 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0    610    159   6983    0    0     0     1    0    0  0  0 100  0  0
 0  0      0    610    159   6983    0    0     0     0   90   96  0  0 100  0  0
 0  0      0    610    159   6983    0    0     0     0   70   71  0  0 100  0  0
 0  0      0    610    159   6983    0    0     0     0   51   61  0  0 100  0  0
 0  0      0    610    159   6983    0    0     0     0  101  111  0  0 100  0  0
 0  0      0    610    159   6983    0    0     0     0   77   87  0  0 100  0  0

可以使用-a参数显示页缓存中活跃或非活跃的内存。

~# vmstat -a -SM 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free  inact active   si   so    bi    bo   in   cs us sy id wa st
 1  0      0    582   4172   2133    0    0     0     1    0    0  0  0 100  0  0
 0  0      0    582   4172   2133    0    0     0     0   84   83  0  0 100  0  0
 0  0      0    582   4172   2133    0    0     0     0   73   81  0  0 100  0  0
 0  0      0    582   4172   2133    0    0     0     0   89  108  0  0 100  0  0
 0  0      0    582   4172   2133    0    0     0     0   82   85  0  0 100  0  0
 0  0      0    582   4172   2133    0    0     0     0   71   79  0  0 100  0  0

可以使用-s参数以列表形式打印内存使用统计。

~# vmstat  -s
      8147216 K total memory
       574224 K used memory
      2184904 K active memory
      4272516 K inactive memory
       597264 K free memory
       155608 K buffer memory
      6820120 K swap cache
            0 K total swap
            0 K used swap
            0 K free swap
       988896 non-nice user cpu ticks
       160614 nice user cpu ticks
       871224 system cpu ticks
   9520067017 idle cpu ticks
       164229 IO-wait cpu ticks
            0 IRQ cpu ticks
       243435 softirq cpu ticks
       555064 stolen cpu ticks
      3918760 pages paged in
     64968211 pages paged out
            0 pages swapped in
            0 pages swapped out
   1195009341 interrupts
   1140494763 CPU context switches
   1644926996 boot time
       980686 forks

PSI

在4.20以后的内核版本中引入了PSI(pressure stall information),包含内存满载度的统计。不仅能显示内存压力,还能显示压力在过去5分钟的变化状态。

~# cat  /proc/pressure/memory
some avg10=2.84 avg60=1.23 avg300=0.32 total=1468344 
full avg10=1.85 avg60=0.66 avg300=0.16 total=702578

从结果中可以看出,内存的压力在逐渐增大。过去10秒的均值大于过去300秒的均值。值代表任务暂停在内存上面的时间占比。some一行显示某些被影响的进程(线程),full一行显示的是所有被影响的进程(线程)。

swapon

swapon命令可以用来查看交换设备是否被配置,如果被配置,则其输出结果会出现在vmstat命令中的si和so列,如果没有配置则命令没有任何输出。
没有配置

~# swapon
~#

有配置

~# swapon
NAME TYPE SIZE USED PRIO 
/dev/dm-2 partition 980M 611.6M -2
/swap1 file 30G 10.9M -3

free

free命令输出系统内存使用的整体结果,如下所示。

~# free
              total        used        free      shared  buff/cache   available
Mem:        8147216      574724      596612        7952     6975880     7260968
Swap:             0           0           0

低版本的centos可能输出的结果不太一样,但是大致是相同的,我们以centos7以后的结果为例讲解:

  1. total:系统总的物理内存和交换空间
  2. used:系统已经使用的物理内核和交换空间
  3. free:系统剩余物理内存和交换空间
  4. shard:被共享使用的物理内存大小,可以包含共享库和/dev/shm下的内容
  5. buff/cache:被buff和cache使用的内存大小
  6. available:还可以被系统使用的内存大小,是一个预测值,并非立即可用大小

可以使用-w参数将buff和cache分开显示,如下所示。

~# free -w
              total        used        free      shared     buffers       cache   available
Mem:        8147216      575416      595828        7952      155744     6820228     7260288
Swap:             0           0           0

ps

ps命令可以显示进程使用的内存,如下所示。

~# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.1 172556 14572 ?        Ss   Feb15   4:09 /sbin/init
root           2  0.0  0.0      0     0 ?        S    Feb15   0:03 [kthreadd]
root           3  0.0  0.0      0     0 ?        I<   Feb15   0:00 [rcu_gp]
root           4  0.0  0.0      0     0 ?        I<   Feb15   0:00 [rcu_par_gp]
root           6  0.0  0.0      0     0 ?        I<   Feb15   0:00 [kworker/0:0H-kblockd]
root           9  0.0  0.0      0     0 ?        I<   Feb15   0:00 [mm_percpu_wq]
root          10  0.0  0.0      0     0 ?        S    Feb15   0:01 [ksoftirqd/0]
root          11  0.0  0.0      0     0 ?        I    Feb15 107:52 [rcu_sched]

其中需要注意的有以下3列:

  1. %MEM:进程使用系统物理内存的百分比
  2. VSZ:虚拟内存大小
  3. RSS:进程正在使用的所有物理内存

ps命令可以通过参数指定输出以上几列,更多信息可以查看/proc//status。

~# ps -eo pid,pmem,vsz,rss | head -10
    PID %MEM    VSZ   RSS
      1  0.1 172556 14572
      2  0.0      0     0
      3  0.0      0     0
      4  0.0      0     0
      6  0.0      0     0
      9  0.0      0     0
     10  0.0      0     0
     11  0.0      0     0
     12  0.0      0     0

pmap

pmap可以通过地址空间段来展示进程内存使用,例如。

~# pmap -x 980436
980436:   (sd-pam)
Address           Kbytes     RSS   Dirty Mode  Mapping
000055e10f8b0000     200       0       0 r---- systemd
000055e10f8e2000     760     128       0 r-x-- systemd
000055e10f9a0000     344      64       0 r---- systemd
000055e10f9f6000     280     260     252 r---- systemd
000055e10fa3c000       4       4       4 rw--- systemd
000055e10fc18000    6300    4192    4192 rw---   [ anon ]
00007fb640000000     132      12      12 rw---   [ anon ]
00007fb640021000   65404       0       0 -----   [ anon ]
00007fb648000000     132      12      12 rw---   [ anon ]
00007fb648021000   65404       0       0 -----   [ anon ]
00007fb64e15d000       8       0       0 r---- pam_gnome_keyring.so
00007fb64e15f000      24       0       0 r-x-- pam_gnome_keyring.so
00007fb64e165000      12       0       0 r---- pam_gnome_keyring.so
00007fb64e168000       4       4       4 r---- pam_gnome_keyring.so
00007fb64e169000       4       4       4 rw--- pam_gnome_keyring.so
00007fb64e16a000       4       0       0 r---- libpam_misc.so.0.82.1
00007fb64e16b000       4       0       0 r-x-- libpam_misc.so.0.82.1
00007fb64e16c000       4       0       0 r---- libpam_misc.so.0.82.1

-x参数可以增加Dirty一列,将展示已经被修改但是还没写回的页。

slabtop

slabtop命令可以打印出内核slab分配器缓存的使用情况,与top类似,实时输出,例如。

 ~# slabtop -sc 
 Active / Total Objects (% used)    : 3682298 / 4152088 (88.7%)
 Active / Total Objects (% used)    : 3682337 / 4152127 (88.7%)
 Active / Total Slabs (% used)      : 116386 / 116386 (100.0%)
 Active / Total Caches (% used)     : 104 / 157 (66.2%)
 Active / Total Size (% used)       : 845034.22K / 961201.15K (87.9%)
 Minimum / Average / Maximum Object : 0.01K / 0.23K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME
327265 286556  87%    1.07K  11285       29    361120K ext4_inode_cache
1484886 1370300  92%    0.10K  38074       39    152296K buffer_head
645351 438645  67%    0.19K  30731       21    122924K dentry
136668 114266  83%    0.57K   4881       28     78096K radix_tree_node
 59228  57655  97%    0.59K   2278       26     36448K inode_cache
169080 168393  99%    0.13K   5636       30     22544K kernfs_node_cache
  4176   4149  99%    4.00K    522        8     16704K kmalloc-4k
 77493  76762  99%    0.20K   1987       39     15896K vm_area_struct
361794 328979  90%    0.04K   3547      102     14188K ext4_extent_status
199808 193039  96%    0.06K   3122       64     12488K vmap_area
 47424  45219  95%    0.25K   1482       32     11856K filp
 10336  10006  96%    1.00K    323       32     10336K kmalloc-1k

-sc参数是指定使用cache size进行排序,使用最多的排在上面。
slab统计信息来自/proc/slabinfo,也可以通过vmstat -m命令打印。

numastat

numastat可以打印在NUMA架构内存访问,以下是有2颗CPU的输出结果。

$ numastat
                           node0           node1
numa_hit          22803297317096  16241914532514
numa_miss           216025857428   7470170746474
numa_foreign       7470170746376    216025857426
interleave_hit         100698246       143800065
local_node        22803189089828  16242714785561
other_node          216134084696   7469370493427

numastat命令可以通过以下命令安装yum install numactl。关键信息如下:

  1. numa_hit:内存分配在local节点
  2. numa_miss+numa_foregin:内存分配在非优先选择的节点
  3. other_node:内存分配在这个节点,但是进程运行在另外一个节点。

    top

    top命令会显示正在运行的程序和程序使用的内存信息。如下所示。

    ~# top -o %MEM
    top - 17:28:45 up 1262 days,  5:55,  1 user,  load average: 98.05, 91.25, 93.78
    Tasks: 2364 total,   6 running, 2356 sleeping,   0 stopped,   2 zombie
    %Cpu(s): 71.8 us,  8.0 sy,  0.0 ni, 11.6 id,  7.6 wa,  0.0 hi,  1.0 si,  0.0 st
    KiB Mem : 19780201+total,  4733848 free, 14077816+used, 52290016 buff/cache
    KiB Swap:        0 total,        0 free,        0 used. 48779544 avail Mem
    
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    10893 yarn      20   0 12.590g 0.010t  14008 S 420.0  5.5  68:55.24 java
    154889 yarn      20   0 14.960g 0.010t  15396 S 625.0  5.3  18847:13 java
    186790 yarn      20   0 14.960g 0.010t  15396 R  10.0  5.3   0:00.02 java
    156061 yarn      20   0 10.996g 9.143g  13600 S   5.0  4.8 787:21.09 java
    117171 yarn      20   0 10.399g 8.717g  13564 S   0.0  4.6 655:34.94 java
    193316 yarn      20   0 10.840g 8.668g  13508 S   0.0  4.6  33:14.55 java
    

    输出结果中,通过-o %MEM参数对输出结果按照内存使用量大小进行了逆序排序。

    sar

    sar(system activity reporter),可以用来观测当前系统的活动。Linux版本用来进行内存观测的参数如下:

  4. -B:换页统计

  5. -H:大页统计
  6. -r:内存使用率
  7. -S:交换分区统计
  8. -W:swapping统计

各项统计信息说明:

选项 统计 描述 单位
-B pgpgin/s 换入 Kbytes/s
pgpgout/s 换出 Kbytes/s
fault/s major fault和minor fault Count/s
majflt/s major fault Count/s
pgfree/s 加到free list的页数量 Count/s
pgscank/s kwapd进程后台扫描的页数量 Count/s
pgscand/s 直接扫描的页数量 Count/s
pgsteal/s 页和交换缓存回收数 Count/s
%vmeff pg steal/pg scan的占比,可以反应页回收的效率 Percent
-H kbhugfree 空闲大页内存 Kbytes
kbhugused 大页使用量 Kbytes
%hugused 大页使用比例 Percent
-r kbmemfree 空闲内存 Kbytes
kbmemused 内存使用大小(不包含内核) Kbytes
%memused 内存使用率 Percent
kbbuffers Buffer cache大小 Kbytes
kbcached Page cache大小 Kbytes
kbcommit
%commit
kbactive Active list内存大小 Kbytes
kbinact Inactive list内存大小 Kbytes
kbdirty 需要写回磁盘的脏内存大小 Kbytes
-S kbswpfree 空闲swap空间 Kbytes
kbswpused 使用swap空间 Kbytes
%swpused 使用swap比例 Percent
kbswpcad cache的swap空间 Kbytes
%swpcad cache的swap空间占已使用的比例 Percent
-W pswpin/s swap换入页数量 Pages/s
pswpout/s swap换出页数量 Pages/s

sar -B

# sar -B 1
Linux 3.10.0-693.5.2.el7.x86_64 (xxxxxxx)     07/04/2022     _x86_64_    (56 CPU)

07:44:31 PM  pgpgin/s pgpgout/s   fault/s  majflt/s  pgfree/s pgscank/s pgscand/s pgsteal/s    %vmeff
07:44:32 PM   8232.00      0.00 448092.00      0.00  35553.00      0.00      0.00      0.00      0.00
07:44:33 PM  24068.00    208.00 508789.00      0.00  35398.00      0.00      0.00      0.00      0.00
07:44:34 PM 140120.00  43032.00 838535.00      0.00  52782.00      0.00      0.00      0.00      0.00
07:44:35 PM 182600.00   8600.00 780663.00      0.00  69181.00      0.00      0.00      0.00      0.00
07:44:36 PM  96316.00    224.00 538031.00      0.00  41329.00      0.00      0.00      0.00      0.00
07:44:37 PM   8276.00    732.00 575471.00      0.00  38922.00      0.00      0.00      0.00      0.00
07:44:38 PM  16172.00  24128.00 450544.00      0.00  49388.00      0.00      0.00      0.00      0.00
07:44:39 PM   8336.00     96.00 716726.00      0.00  36242.00      0.00      0.00      0.00      0.00
07:44:40 PM   8340.00     76.00 637336.00      0.00  60962.00      0.00      0.00      0.00      0.00
^C

07:44:41 PM  49796.92    467.69 591041.54      0.00  42863.08      0.00      0.00      0.00      0.00
Average:     54386.32   8020.73 609156.89      0.00  46385.28      0.00      0.00      0.00      0.00

sar -H

# sar -H 1
Linux 3.10.0-693.5.2.el7.x86_64 (xxxxxxx)     07/04/2022     _x86_64_    (56 CPU)

07:45:22 PM kbhugfree kbhugused  %hugused
07:45:23 PM         0         0      0.00
07:45:24 PM         0         0      0.00
07:45:25 PM         0         0      0.00
07:45:26 PM         0         0      0.00
^C

07:45:27 PM         0         0      0.00
Average:            0         0      0.00

sar -r

# sar -r 1
Linux 3.10.0-693.5.2.el7.x86_64 (xxxxxxx)     07/04/2022     _x86_64_    (56 CPU)

07:45:57 PM kbmemfree kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
07:45:58 PM  13626744 184165092     93.11     65460  60734772 136710732     69.12 131185828  48408768     38588
07:45:59 PM  13560152 184231684     93.14     65460  60790900 136710332     69.12 131189220  48465552     34664
07:46:00 PM  13518588 184273248     93.17     65460  60805216 136753604     69.14 131207576  48479612     48208
07:46:01 PM  13416536 184375300     93.22     65468  60813200 139314084     70.43 131274552  48486932     48676
07:46:02 PM  12874604 184917232     93.49     65476  60857060 169497468     85.69 131738296  48534912     31128
07:46:03 PM  12867388 184924448     93.49     65476  60854772 139385412     70.47 131804308  48533416     30860
07:46:04 PM  12623376 185168460     93.62     65476  60844268 141125696     71.35 132056892  48523128     32892
^C

07:46:04 PM  12651424 185140412     93.60     65476  60845112 141102516     71.34 132028816  48523196     32988
Average:     13142352 184649484     93.36     65469  60818162 142574980     72.08 131560686  48494440     37250

sar -S

# sar -S 1
Linux 3.10.0-693.5.2.el7.x86_64 (xxxxxxx)     07/04/2022     _x86_64_    (56 CPU)

07:46:46 PM kbswpfree kbswpused  %swpused  kbswpcad   %swpcad
07:46:47 PM         0         0      0.00         0      0.00
07:46:48 PM         0         0      0.00         0      0.00
07:46:49 PM         0         0      0.00         0      0.00
07:46:50 PM         0         0      0.00         0      0.00
07:46:51 PM         0         0      0.00         0      0.00
07:46:52 PM         0         0      0.00         0      0.00
^C

07:46:52 PM         0         0      0.00         0      0.00
Average:            0         0      0.00         0      0.00

sar -W

# sar -W 1
Linux 3.10.0-693.5.2.el7.x86_64 (xxxxxxx)     07/04/2022     _x86_64_    (56 CPU)

07:46:55 PM  pswpin/s pswpout/s
07:46:56 PM      0.00      0.00
07:46:57 PM      0.00      0.00
07:46:58 PM      0.00      0.00
07:46:59 PM      0.00      0.00
07:47:00 PM      0.00      0.00
^C

07:47:01 PM      0.00      0.00
Average:         0.00      0.00

perf

perf是Linux下面一个非常强大的工具,我们之前在CPU的章节中已经介绍过perf有关的用法,本次我们只讲解perf在内存方面的使用。

perf 单行命令
  1. 记录10s 缺页异常的事件数据

    perf record -e page-faults -a -g -- sleep 10
    [ perf record: Woken up 114 times to write data ]
    [ perf record: Captured and wrote 38.086 MB perf.data (178308 samples) ]
    
  2. 记录PID为23912进程10秒缺页异常,连带栈信息

    # perf record -e page-faults -c 1 -p 23912 -g -- sleep 10
    [ perf record: Woken up 1 times to write data ]
    [ perf record: Captured and wrote 0.287 MB perf.data (129 samples) ]
    # perf script
    Failed to open /tmp/perf-23912.map, continuing without symbols
    java 52494 109151486.674671: page-faults:
                   654e4b CollectedHeap::common_mem_allocate_init (/usr/java/jdk1.8.0_102/jre/lib/amd64/server/libjvm.so)
                   6433cc InstanceKlass::allocate_instance (/usr/java/jdk1.8.0_102/jre/lib/amd64/server/libjvm.so)
                   9b949e OptoRuntime::new_instance_C (/usr/java/jdk1.8.0_102/jre/lib/amd64/server/libjvm.so)
             7f83b05fb685 [unknown] (/tmp/perf-23912.map)
    
  3. 记录brk系统调用

    # perf  record -e syscalls:sys_enter_brk -a -g -- sleep 60
    
  4. 追踪kwapd进程唤醒事件,使用Ctrl+C结束

    # perf record -e vmscan:mm_vmscan_wakeup_kswapd -ag
    ^C[ perf record: Woken up 1 times to write data ]
    [ perf record: Captured and wrote 0.694 MB perf.data ]
    

    使用perf工具生成火焰图
    # perf record -e page-faults -a -g -- sleep 10
    # perf script --header > out.sshd.stacks
    # git clone https://github.com/brendangregg/FlameGraph
    # ./FlameGraph/stackcollapse-perf.pl < out.sshd.stacks | ./FlameGraph/flamegraph.pl --hash --bgcolor=green --count=pages --title="SSHD Page Fault Flame Graph" > sshd.out.svg
    

    image.png

    BPF工具

    BPF和bpftrace工具有非常多的内存分析工具,下面我们一一介绍。
    image.png

    oomkill

    追踪OOM Killer事件信息
    BCC 版本

    # oomkill-bpfcc
    Tracing OOM kills... Ctrl-C to stop.
    12:35:36 Triggered by PID 987694 ("python3"), OOM kill of PID 987694 ("python3"), 2036804 pages, loadavg: 0.17 0.04 0.01 5/435 987722
    

    测试OOM程序

    ~# python3
    Python 3.8.10 (default, Mar 15 2022, 12:22:08)
    [GCC 9.4.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> KB=1024
    >>> MB=1024*KB
    >>> GB=1024*MB
    >>> "aaaaaaa"*GB
    Killed
    ~# dmesg -T | grep Out
    [Mon Jul  4 12:32:29 2022] Out of memory: Killed process 987694 (python3) total-vm:7384756kB, anon-rss:7308836kB, file-rss:848kB, shmem-rss:0kB, UID:0 pgtables:14388kB oom_score_adj:0
    

    bpftrace版本 ```bash

    oomkill.bt

Attaching 2 probes… Tracing oom_kill_process()… Hit Ctrl-C to end. 12:53:05 Triggered by PID 987810 (“systemd-udevd”), OOM kill of PID 0 (“”), 1280070982 pages, loadavg: 0.52 0.11 0.04 5/410 987811

测试OOM程序同BCC版本的测试程序<br />bpftrace版本代码
```bash
#include <linux/oom.h>

BEGIN
{
    printf("Tracing oom_kill_process()... Hit Ctrl-C to end.\n");
}

kprobe:oom_kill_process
{
    $oc = (struct oom_control *)arg1;
    time("%H:%M:%S ");
    printf("Triggered by PID %d (\"%s\"), ", pid, comm);
    printf("OOM kill of PID %d (\"%s\"), %d pages, loadavg: ",
        $oc->chosen->pid, $oc->chosen->comm, $oc->totalpages);
    cat("/proc/loadavg");
}

memleak

memleak是一个BCC程序,当前还没有bpftrace版本。
展示内存分配和释放信息

~# memleak-bpfcc -p 2582811
Attaching to pid 2582811, Ctrl+C to quit.
[14:21:43] Top 10 stacks with outstanding allocations:
[14:21:48] Top 10 stacks with outstanding allocations:
[14:21:53] Top 10 stacks with outstanding allocations:
[14:21:58] Top 10 stacks with outstanding allocations:

命令行语法

memleak [options] [-p PID] [-c COMAND] [interval [count]]

命令行选项

  • -s RATE:采样频率,使用RATE分之一的采样频率来降低额外消耗
  • -o OLDER:忽略哪些存活时间小于OLDER毫秒的内存分配

内存分配,尤其是用户态的内存分配可能会是非常频繁的,这个命令可能会导致性能下降至原来的十分之一以下

brkstack

brk系统调用可以通过BCC的stackcount进行监听和统计,stackcount是BCC版本的trace,当然这个系统调用也是可以通过perf获取的。

~# stackcount-bpfcc -PU t:syscalls:sys_enter_brk
Tracing 1 functions for "t:syscalls:sys_enter_brk"... Hit Ctrl-C to end.
^C
  b'[unknown]'
  b'[unknown]'
    b'python3' [987856]
    1

  b'[unknown]'
  b'[unknown]'
    b'command-not-fou' [987855]

bpftrace版本主要代码

t:syscalls:sys_enter_brk { @[ustack, comm] = count(); }

可以通过如下方式执行

~# bpftrace -e 't:syscalls:sys_enter_brk { @[ustack, comm] = count(); }'
Attaching 1 probe...
^C

@[
    0x7fabfce12f3b
    0x41e589480000b264
, python3]: 1
@[
    0x7fabfcd042bb
, python3]: 12

shmsnoop

跟踪System V的共享内存系统调用:shmget、shmat、shmdt以及shmctl
命令使用语法

shmsnoop [options]

命令行选项

  • -T:包含时间戳信息
  • -p PID:仅关注给定的进程

shmsnoop抓取shmget和shmat调用

~# shmsnoop-bpfcc  -T
TIME(s)       PID    COMM                SYS              RET ARGs
0.000000000   988552 shm_test         SHMGET                9 key: 0x0, size: 1024, shmflg: 0x180 (0600)
0.000142000   988552 shm_test          SHMAT     7fe7322ea000 shmid: 0x9, shmaddr: 0x0, shmflg: 0x0
1.000484000   988553 shm_test          SHMAT     7fe7322ea000 shmid: 0x9, shmaddr: 0x0, shmflg: 0x0
8.432580000   988556 shm_test         SHMGET                a key: 0x0, size: 1024, shmflg: 0x180 (0600)
8.432749000   988556 shm_test          SHMAT     7f1bda58c000 shmid: 0xa, shmaddr: 0x0, shmflg: 0x0
9.433112000   988557 shm_test          SHMAT     7f1bda58c000 shmid: 0xa, shmaddr: 0x0, shmflg: 0x0

调用shmget和shmat的C代码

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/wait.h>

#define PERM (S_IRUSR | S_IWUSR)

int main ( int argc, char **argv ) {
    int shmid;
    char *p_addr, *c_addr;

    if ( argc != 2 ) {
        fprintf ( stderr, "Usage: %s\n", argv[0] );
        exit ( 1 );
    }

    if ( ( shmid = shmget ( IPC_PRIVATE, 1024, PERM ) ) == -1 ) { /* 创建共享内存 */
        fprintf ( stderr, "Create Share Memory Error: %s\n", strerror ( errno ) );
        exit ( 1 );
    }

    if ( fork() ) { /* 父进程写 */
        p_addr = ( char * ) shmat ( shmid, NULL, 0 );
        memset ( p_addr, '\0', 1024 );
        strncpy ( p_addr, argv[1], 1024 );
        wait ( NULL );
        exit ( 0 );
    } else { /* 子进程读 */
        sleep ( 1 ); /* 暂停1秒 */
        c_addr = ( char * ) shmat ( shmid, NULL, 0 );
        printf ( "Client get %s\n", c_addr );
        exit ( 0 );
    }
}

编译&运行C代码

# ./shm_test abcd
Client get abcd
# ./shm_test abcd
Client get abcd

faults

缺页异常和对应的调用栈信息,可以用BCC的stackcount工具对用户态和内核台的缺页异常进行统计。
用户态统计

~# stackcount-bpfcc -PU t:exceptions:page_fault_user
Tracing 1 functions for "t:exceptions:page_fault_user"... Hit Ctrl-C to end.
^C
  b'[unknown]'
  b'[unknown]'
    b'stackcount-bpfc' [988657]
    1

Detaching...

内核态统计

~# stackcount-bpfcc  t:exceptions:page_fault_kernel
Tracing 1 functions for "t:exceptions:page_fault_kernel"... Hit Ctrl-C to end.
^C
  b'do_page_fault'
  b'do_page_fault'
  b'do_async_page_fault'
  b'async_page_fault'
  b'__clear_user'
  b'clear_user'
  b'load_elf_binary'
  b'search_binary_handler'
  b'load_script'
  b'search_binary_handler'
  b'__do_execve_file.isra.0'
  b'__x64_sys_execve'
  b'do_syscall_64'
  b'entry_SYSCALL_64_after_hwframe'
  b'[unknown]'
    1

缺页异常生成火焰图

~# stackcount-bpfcc -f -PU t:exceptions:page_fault_user > out.pagefault.txt
~# cat out.pagefault.txt
stackcount-bpfc;[unknown];[unknown] 1
gsd-color;[unknown];[unknown];[unknown];[unknown];[unknown];[unknown];[unknown];g_time_zone_new 4
~# ./FlameGraph/flamegraph.pl --hash --width=800 --title="Page Fault Flame Page" --colors=java --bgcolor=green < out.pagefault.txt >out.pagefault.svg

image.png

ffaults

ffaults是一个bpftrace程序,使用该程序可以根据文件名来跟踪缺页异常,代码如下(有bug尚未修复):

#!/usr/bin/bpftrace

#include <linux/mm.h>

kprobe:handle_mm_fault
{
    $vma = (struct vm_area_struct *)arg0;
    $file = $vma->vm_file->f_path.dentry->d_name.name;
    @[str($file)] = count();
}

执行结果如下

 ./ffaults.bt
Attaching 1 probe...
^C

@[]: 4701

drsnoop

用来跟踪内存释放过程中的直接回收部分

~# drsnoop-bpfcc -T
TIME(s)       COMM           PID     LAT(ms) PAGES

命令行语法

drsnoop [options]

命令行选项

  • -T:包括时间戳信息
  • -p PID:仅关注给定的进程

    llcstat-bpfcc

    按进程显示末级缓存的命中率
    ~# llcstat-bpfcc
    Running for 10 seconds or hit Ctrl-C to end.
    PID      NAME             CPU     REFERENCE         MISS    HIT%
    0        swapper/4        4             100          200   0.00%
    0        swapper/1        1             200          200   0.00%
    0        swapper/6        6             100          200   0.00%
    9769     llcstat-bpfcc    7             200          300   0.00%
    0        swapper/0        0             200          200   0.00%
    0        swapper/5        5             100          200   0.00%
    0        swapper/3        3             100          200   0.00%
    0        swapper/2        2             100          200   0.00%
    Total References: 1100 Total Misses: 1700 Hit Rate: -54.55%
    

BPF单行程序

BCC

  1. 根据用户态调用栈信息统计进程堆内存扩展(brk())

stackcount -U t:syscalls:sys_enter_brk

  1. 根据用户态调用栈信息统计缺页错误

stackcount -U t:exceptions:page_fault_user

  1. 通过跟踪点来统计vmscan操作

funccount 't:vmscan:*'

  1. 按进程展示hugepage_madvise()调用:

trace hugepage_madvise

  1. 统计页迁移事件

funccount t:migrate:mm_migrate_pages

  1. 统计页压缩事件

tarce t:compaction:mm_compaction_begin

bpftrace

  1. 根据用户态调用栈信息统计进程堆内存扩展(brk())

bpftrace -e 't:syscalls:sys_enter_brk { @[ustack, comm] = count(); }'

  1. 按进程统计缺页错误

bpftrace -e 'software:page-faults:1 { @[comm, pid] = count(); }'

  1. 根据用户态调用栈信息统计缺页错误

bpftrace -e 't:exceptions:page_fault_user { @[ustack, comm] = count(); }'

  1. 通过跟踪点来统计vmscan操作

bpftrace -e 't:vmscan:* { @[probe] = count(); }'

  1. 按进程展示hugepage_madvise()调用:

bpftrace -e 'k:hugepage_madvise { printf ("%s by PID %d\n", probe, pid); }'

  1. 统计页迁移事件

bpftrace -e 't:migrate:mm_migrate_pages { @ = count(); }'

  1. 统计页压缩事件

bpftrace -e 't:compaction:mm_compaction_begin { time(); }'

Systemtap工具

systemtap工具无法与BPF工具一一对应,中途需要不少的开发工作,本次我们挑选的是systemtap默认自带的example来做讲解。

glibc-malloc

该程序对glibc malloc函数的内部实现做统计,通过-x/-c指定被统计的进程,如下所示。

~# stap  glibc-malloc.stp -c "ls -l"
total 168
-rw-r--r--. 1 root root  381 Oct 14  2018 glibc-malloc.meta
-rw-r--r--. 1 root root 2491 Oct 14  2018 glibc-malloc.stp
[...]
-rwxr-xr-x. 1 root root  419 Oct 14  2018 vm.tracepoints.stp
-rw-r--r--. 1 root root  557 Oct 14  2018 vm.tracepoints.txt
malloc information for pid 16243
Contention:
Active arenas:
Allocated heaps:
Total sbrk: 135168 bytes
Mmap threshold in the end: 128 kb

pfaults

> stap pfaults.stp
2972446:8994:140735039790248d:w:minor:10
2972569:18551:93958907995264d:w:minor:14
2972582:18551:93958888569296d:r:minor:6
2972592:18551:93958888510448d:r:minor:1
2972637:18551:93958888554608d:r:minor:2
2972659:18551:93958888468416d:r:minor:2