目录

4.9 文件系统动态追踪

本节我们来介绍文件系统的动态追踪技术

1. 使用 Systemtap 进行文件系统分析

Dtrace/Systemtap 能从系统调用、VFS 接口或文件系统内部的角度来查看文件系统行为。这些功能能用在负载特征分析和延时分析上。

1.1 操作计数

按照应用程序和类型统计文件系统操作,为负载特征归纳提供有用测量信息。

DTrace

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Solaris
# 1. 按照应用程序名统计文件系统操作
> dtrace -n 'fsinfo:::{@[execname] = count();}'
# 2. 按秒统计
> dtrace -n 'fsinfo:::{@[execname] = count();} tick-1s{printa(@);}'
# 3. 按 probename 统计
> dtrace -n 'fsinfo::: /execname == "splunkd"/ { @[probename] = count();}'

# Linux
# 1. fsinfo provider 无法使用,文件系统操作可以通过 syscall 和 fbt provider 观察
> dtrace -n 'fbt::vfs_*:entry { @[execname] = count(); }'
> dtrace -n 'fbt::vfs_*:entry /execname == "sysbench"/ { @[probename] = count(); }'

Systemtap

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 1.
> stap -ve 'global c; probe kernel.function("vfs_*") { c[execname()] <<< 1} 
            probe end {foreach (k in c+){printf("%-36s %d\n", k, @count(c[k]))}}'
# 2. 每秒统计
> stap -ve 'global c; probe begin, timer.s(1) {printf("%-36s %s\n", "execname", "count")} 
            probe kernel.function("vfs_*") { c[execname()] <<< 1}
            probe timer.s(1) {foreach (k in c+){printf("%-36s %d\n", k, @count(c[k]))};
                              delete c}'
# 3. 按probename 统计
> stap -ve 'global c; probe kernel.function("vfs_*")
                      { if ( execname() == "sshd" ){ c[probefunc()] <<< 1};}'

1.2 文件打开

DtraceToolkit 工具箱中包含了如下的工具:

  1. opensnoop: 显示了进程打开的所有文件,和错误信息
  2. rwsnoop: 跟踪和统计逻辑 I/O,包括 read()和write() 系统调用
  3. rwtop: 跟踪和统计逻辑 I/O,使用 sysinfo proveder 统计吞吐量

DTrace

1
2
3
> opensnoop
> rwsnoop
> rwtop

Linux

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
> yum install bcc
> rpm -ql bcc-tools
> cd /usr/share/bcc/tools
> ./opensnoop
PID    COMM               FD ERR PATH
506    systemd-journal    22   0 /proc/2016/cgroup
506    systemd-journal    22   0 /proc/2016/comm
506    systemd-journal    22   0 /proc/2016/cmdline
506    systemd-journal    22   0 /proc/2016/status
506    systemd-journal    22   0 /proc/2016/sessionid

1.3 系统调用延时

DTrace

1
2
3
4
5
# 1. 系统调用接口级别测量了文件系统的超时
> dtrace -n 'syscall::read:entry /fds[arg0].fi_fs == "zfs"/ 
             {self->start = timestamp;}
             syscall::read:return /self->start/ 
             {@["ns"] = quantize(timestamp - self.start);self->start = 0}'

说明:

  1. 这个方法跟踪单个系统调用 read(),为了捕获所有的系统调用,所有系统调用都要跟踪包括他们的变体
  2. 这个方法跟踪了 zfs 文件系统的活动,也可以跟踪其他文件系统包括,非存储类型的文件系统入 sockfs
  3. 如果应用程序使用非阻塞I/O或者这是一个后台异步的后台任务,可能并不会应用程序性能产生影响
  4. 通过捕获用户态系统调用 I/O 的调用栈可以更准确的反映应用程序性能,比如使用 @[ustack(), ’ns’],不过这是一个耗时操作深度调查

Systemp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 1. 获取 read 系统调用的文件系统
stap -ve '
    probe syscall.read { 
        file = @cast(task_current(), "task_struct")->
            files->fdt->fd[fd] & ~3; 
        if(!file) 
            next; 
        dentry = @cast(file, "file")->f_path->dentry;  
        inode = @cast(dentry, "dentry")->d_inode;
        device = kernel_string(@cast(inode, "inode")->i_sb->s_id);
        filesystem_type = kernel_string(@cast(dentry, "dentry")->d_sb->s_type->name);
        
        printf("READ %d: file '%s' of size '%d' on device: %s, with filesystem: %s  \n", 
            fd, d_name(dentry), @cast(inode, "inode")->i_size,
            device, filesystem_type); 
    } '  -c 'cat /etc/passwd > /dev/null'

# 2. 统计 xfs 文件系统级别 read 的调用延迟
stap -ve 'global s; probe syscall.read.return {
          file = @cast(task_current(), "task_struct")->files->fdt->fd[fd] & ~3; 
          if(!file) 
            next; 
          dentry = @cast(file, "file")->f_path->dentry; 
          filesystem_type = kernel_string(@cast(dentry, "dentry")->d_sb->s_type->name);
          if (filesystem_type == "xfs")
          {s  <<< gettimeofday_ns() - @entry(gettimeofday_ns());}}
          probe end{print(@hist_log(s))}'

1.4 VFS 缓存

DTrace

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 1. Solaris 上 VFS 可通过 fop_* 函数跟踪
> dtrace -ln 'fbt::fop_*:entry'
# 可以匹配所有的读调用变体
> dtrace -n 'fbt::ftop_read:entry /stringof(arg[0]->v_op_>vnop_name) == "zfs"/ 
             {self->start = timestamp;}
             fbt::ftop_read:return /self->start/ 
             {@["ns"] = quantize(timestamp - self.start);self->start = 0}' 
             
# 2. Linux 
> dtrace -n 'fbt::vfs_read:entry 
 /stringof(((struct file *)arg0)->f_path.dentry->d_sb->s_type->name) == "ext4"/'
 {self->start = timestamp;}
 fbt::vfs_read:return /self->start/ 
 {@["ns"] = quantize(timestamp - self.start);self->start = 0}' 

Systemp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 1. 获取 read 系统调用的文件系统
# 2. 统计 xfs 上所有读调用的延迟
> stap -ve 'global s; probe kernel.function("vfs_read").return {
          file = @cast(task_current(), "task_struct")->files->fdt->fd[fd] & ~3; 
          if(!file) 
            next; 
          dentry = @cast(file, "file")->f_path->dentry; 
          filesystem_type = kernel_string(@cast(dentry, "dentry")->d_sb->s_type->name);
          if (filesystem_type == "xfs")
          {s  <<< gettimeofday_ns() - @entry(gettimeofday_ns());}}
          probe end{print(@hist_log(s))}'
# 3. 列出 VFS 函数入口
> stap -l 'kernel.function("vfs_*")'

1.5 块设备 I/O调用栈

查看块设备 I/O调用栈和发出磁盘 I/O 的代码路径,是理解文件系统内部工作机制的绝佳方法。

DTrace

1
2
# 1. # 统计发出块设备 I/O 时内核调用栈的内容及次数
> dtrace 'io:::start { @[stack()] =count();}'

Systemp

参考: https://groups.google.com/forum/#!topic/openresty/u-puKWWONMk

1
2
3
4
# 1. 未成功
> stap -ve 'global s; probe ioblock.request { s[backtrace()] <<< 1;}
            probe end { foreach (k in s- limit 1000)
            {print(@count(s[k]))};}'

1.6 跟踪文件系统内部

对于同步读,直接跟踪文件系统的内核函数是可行的,但是对于异步执行的I/O操作,测量读延时 I/O 发起的和结束时间需要一一关联和对比,或者跟踪更高一级调用栈。

DTrace

1
2
3
4
5
6
7
# 1. 列出 zfs 内核函数 
> dtrace -ln 'fbt:zfs::entry'
# 2. 跟踪 zfs 同步读的读延时
> dtrace -n 'fbt::zfs_read:entry 
             {self->start = timestamp;}
             syscall::read:return /self->start/ 
             {@["ns"] = quantize(timestamp - self.start);self->start = 0}'

Systemp

1
2
# 1. 未找到 xfs 内核函数
> 

1.7 慢事件跟踪

由于文件系统缓存命中,在文件系统级别跟踪会产生大量的输出。一个解决办法是仅仅打印出慢操作。

DTrace

1
> ./zfsslower.d

Systemp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
> cd /user/share/bcc/tools
 ll|grep lower
-rwxr-xr-x. 1 root root 10096 8月   9 2019 btrfsslower
-rwxr-xr-x. 1 root root  7321 1月  12 2019 dbslower
-rwxr-xr-x. 1 root root 10431 8月   9 2019 ext4slower
-rwxr-xr-x. 1 root root  7712 8月   9 2019 fileslower
-rwxr-xr-x. 1 root root 10726 1月  12 2019 funcslower
-rwxr-xr-x. 1 root root  3286 1月  12 2019 mysqld_qslower
-rwxr-xr-x. 1 root root  9550 8月   9 2019 nfsslower
-rwxr-xr-x. 1 root root  7396 8月   9 2019 runqslower
-rwxr-xr-x. 1 root root  8397 8月   9 2019 xfsslower

> ./xfsslower

1.8 高级跟踪:

DTrace

文件系统常用的高级跟踪脚本:

  1. DTrace: https://github.com/brendangregg/DTrace-book-scripts/tree/master/Chap5
  2. Systemp: https://sourceware.org/systemtap/SystemTap_Beginners_Guide/mainsect-disk.html