本节我们来介绍文件系统的动态追踪技术
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 工具箱中包含了如下的工具:
- opensnoop: 显示了进程打开的所有文件,和错误信息
- rwsnoop: 跟踪和统计逻辑 I/O,包括 read()和write() 系统调用
- 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}'
|
说明:
- 这个方法跟踪单个系统调用 read(),为了捕获所有的系统调用,所有系统调用都要跟踪包括他们的变体
- 这个方法跟踪了 zfs 文件系统的活动,也可以跟踪其他文件系统包括,非存储类型的文件系统入 sockfs
- 如果应用程序使用非阻塞I/O或者这是一个后台异步的后台任务,可能并不会应用程序性能产生影响
- 通过捕获用户态系统调用 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.7 慢事件跟踪
由于文件系统缓存命中,在文件系统级别跟踪会产生大量的输出。一个解决办法是仅仅打印出慢操作。
DTrace
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
文件系统常用的高级跟踪脚本:
- DTrace: https://github.com/brendangregg/DTrace-book-scripts/tree/master/Chap5
- Systemp: https://sourceware.org/systemtap/SystemTap_Beginners_Guide/mainsect-disk.html